File: //usr/lib/nagios/plugins/check_raid
#!/usr/bin/perl
# nagios: -epn
# This chunk of stuff was generated by App::FatPacker. To find the original
# file's code, look for the end of this BEGIN block or the string 'FATPACK'
BEGIN {
my %fatpacked;
$fatpacked{"App/Monitoring/Plugin/CheckRaid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID';
package App::Monitoring::Plugin::CheckRaid;
use Carp qw(croak);
use Module::Pluggable 5.1 instantiate => 'new', sub_name => '_plugins';
use strict;
use warnings;
# constructor
sub new {
my $class = shift;
croak 'Odd number of elements in argument hash' if @_ % 2;
my $self = {
@_,
};
my $obj = bless $self, $class;
# setup search path for Module::Pluggable
$self->search_path(add => __PACKAGE__ . '::Plugins');
# setup only certain plugins
if ($self->{enable_plugins}) {
my @plugins = map {
__PACKAGE__ . '::Plugins::' . $_
} @{$self->{enable_plugins}};
$self->only(\@plugins);
}
return $obj;
}
# create list of plugins
sub plugins {
my ($this) = @_;
# call this once
if (!defined $this->{plugins}) {
my @plugins = $this->_plugins(%$this);
$this->{plugins} = \@plugins;
}
wantarray ? @{$this->{plugins}} : $this->{plugins};
}
# get plugin by name
sub plugin {
my ($this, $name) = @_;
if (!defined $this->{plugin_names}) {
my %names;
foreach my $plugin ($this->plugins) {
my $name = $plugin->{name};
$names{$name} = $plugin;
}
$this->{plugin_names} = \%names;
}
croak "Plugin '$name' Can not be created" unless exists $this->{plugin_names}{$name};
$this->{plugin_names}{$name};
}
# Get active plugins.
# Returns the plugin objects
sub active_plugins {
my $this = shift;
my @plugins = ();
# go over all registered plugins
foreach my $plugin ($this->plugins) {
# skip if no check method (not standalone checker)
next unless $plugin->can('check');
# skip inactive plugins (disabled or no tools available)
next unless $plugin->active;
push(@plugins, $plugin);
}
return wantarray ? @plugins : \@plugins;
}
1;
APP_MONITORING_PLUGIN_CHECKRAID
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugin.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGIN';
package App::Monitoring::Plugin::CheckRaid::Plugin;
use Carp qw(croak);
use App::Monitoring::Plugin::CheckRaid::Utils;
use strict;
use warnings;
# Nagios standard error codes
my (%ERRORS) = (OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3);
# default plugin options
our %options = (
# status to set when RAID is in resync state
resync_status => $ERRORS{WARNING},
# Status code to use when no raid volumes were detected
noraid_status => $ERRORS{UNKNOWN},
# status to set when RAID is in check state
check_status => $ERRORS{OK},
# status to set when PD is spare
spare_status => $ERRORS{OK},
# status to set when BBU is in learning cycle.
bbulearn_status => $ERRORS{WARNING},
# status to set when Write Cache has failed.
cache_fail_status => $ERRORS{WARNING},
# check status of BBU
bbu_monitoring => 0,
);
# return list of programs this plugin needs
# @internal
sub program_names {
}
# return hash of canonical commands that plugin can use
# @internal
sub commands {
{}
}
# return sudo rules if program needs it
# may be SCALAR or LIST of scalars
# @internal
sub sudo {
();
}
# constructor for plugins
sub new {
my $class = shift;
croak 'Odd number of elements in argument hash' if @_ % 2;
croak 'Class is already a reference' if ref $class;
# convert to hash
my %args = @_;
# merge 'options' from param and class defaults
my %opts = %options;
%opts = (%options, %{$args{options}}) if $args{options};
delete $args{options};
# merge commands
my %commands = %{$class->commands};
%commands = (%commands, %{$args{commands}}) if $args{commands};
delete $args{commands};
my $self = {
commands => \%commands,
sudo => $class->sudo ? find_sudo() : '',
options => \%opts,
%args,
# name of the plugin, without package namespace
name => ($class =~ /.*::([^:]+)$/),
status => undef,
message => undef,
perfdata => undef,
longoutput => undef,
};
my $this = bless $self, $class;
# lookup program, if not defined by params
if (!$self->{program}) {
$self->{program} = which($this->program_names);
}
return $this;
}
# see if plugin is active (disabled or no tools available)
sub active {
my $this = shift;
# no tool found, return false
return 0 unless $this->{program};
# program file must exist, don't check for execute bit. #104
-f $this->{program};
}
# set status code for plugin result
# does not overwrite status with lower value
# returns the current status code
sub status {
my ($this, $status) = @_;
if (defined $status) {
$this->{status} = $status unless defined($this->{status}) and $status < $this->{status};
}
$this->{status};
}
sub set_critical_as_warning {
$ERRORS{CRITICAL} = $ERRORS{WARNING};
}
# helper to set status to WARNING
# returns $this to allow fluent api
sub warning {
my ($this) = @_;
$this->status($ERRORS{WARNING});
return $this;
}
# helper to set status to CRITICAL
# returns $this to allow fluent api
sub critical {
my ($this) = @_;
$this->status($ERRORS{CRITICAL});
return $this;
}
# helper to set status to UNKNOWN
# returns $this to allow fluent api
sub unknown {
my ($this) = @_;
$this->status($ERRORS{UNKNOWN});
return $this;
}
# helper to set status to OK
sub ok {
my ($this) = @_;
$this->status($ERRORS{OK});
return $this;
}
# helper to set status for resync
# returns $this to allow fluent api
sub resync {
my ($this) = @_;
$this->status($this->{options}{resync_status});
return $this;
}
# helper to set status for check
# returns $this to allow fluent api
sub check_status {
my ($this) = @_;
$this->status($this->{options}{check_status});
return $this;
}
# helper to set status for no raid condition
# returns $this to allow fluent api
sub noraid {
my ($this) = @_;
$this->status($this->{options}{noraid_status});
return $this;
}
# helper to set status for spare
# returns $this to allow fluent api
sub spare {
my ($this) = @_;
$this->status($this->{options}{spare_status});
return $this;
}
# helper to set status for BBU learning cycle
# returns $this to allow fluent api
sub bbulearn {
my ($this) = @_;
$this->status($this->{options}{bbulearn_status});
return $this;
}
# helper to set status when Write Cache fails
# returns $this to allow fluent api
sub cache_fail {
my ($this) = @_;
$this->status($this->{options}{cache_fail_status});
return $this;
}
# helper to get/set bbu monitoring
sub bbu_monitoring {
my ($this, $val) = @_;
if (defined $val) {
$this->{options}{bbu_monitoring} = $val;
}
$this->{options}{bbu_monitoring};
}
# setup status message text
sub message {
my ($this, $message) = @_;
if (defined $message) {
# TODO: append if already something there
$this->{message} = $message;
}
$this->{message};
}
# Set performance data output.
sub perfdata {
my ($this, $perfdata) = @_;
if (defined $perfdata) {
# TODO: append if already something there
$this->{perfdata} = $perfdata;
}
$this->{perfdata};
}
# Set plugin long output.
sub longoutput {
my ($this, $longoutput) = @_;
if (defined $longoutput) {
# TODO: append if already something there
$this->{longoutput} = $longoutput;
}
$this->{longoutput};
}
# a helper to join similar statuses for items
# instead of printing
# 0: OK, 1: OK, 2: OK, 3: NOK, 4: OK
# it would print
# 0-2,4: OK, 3: NOK
# takes as input list:
# { status => @items }
sub join_status {
my $this = shift;
my %status = %{$_[0]};
my @status;
for my $status (sort {$a cmp $b} keys %status) {
my $disks = $status{$status};
my @s;
foreach my $disk (@$disks) {
push(@s, $disk);
}
push(@status, join(',', @s).'='.$status);
}
return join ' ', @status;
}
# return true if parameter is not in ignore list
sub valid {
my $this = shift;
my ($v) = lc $_[0];
foreach (@utils::ignore) {
return 0 if lc $_ eq $v;
}
return 1;
}
use constant K => 1024;
use constant M => K * 1024;
use constant G => M * 1024;
use constant T => G * 1024;
sub format_bytes {
my $this = shift;
my ($bytes) = @_;
if ($bytes > T) {
return sprintf("%.2f TiB", $bytes / T);
}
if ($bytes > G) {
return sprintf("%.2f GiB", $bytes / G);
}
if ($bytes > M) {
return sprintf("%.2f MiB", $bytes / M);
}
if ($bytes > K) {
return sprintf("%.2f KiB", $bytes / K);
}
return "$bytes B";
}
# disable sudo temporarily
sub nosudo_cmd {
my ($this, $command, $cb) = @_;
my ($res, @res);
my $sudo = $this->{sudo};
$this->{sudo} = 0;
if (wantarray) {
@res = $this->cmd($command, $cb);
} else {
$res = $this->cmd($command, $cb);
}
$this->{sudo} = $sudo;
return wantarray ? @res : $res;
}
# build up command for $command
# returns open filehandle to process output
# if command fails, program is exited (caller needs not to worry)
sub cmd {
my ($this, $command, $cb) = @_;
my $debug = $App::Monitoring::Plugin::CheckRaid::Utils::debug;
# build up command
my @CMD = $this->{program};
# add sudo if program needs
unshift(@CMD, @{$this->{sudo}}) if $> and $this->{sudo};
my $args = $this->{commands}{$command} or croak "command '$command' not defined";
# callback to replace args in command
my $cb_ = sub {
my $param = shift;
if ($cb) {
if (ref $cb eq 'HASH' and exists $cb->{$param}) {
return wantarray ? @{$cb->{$param}} : $cb->{$param};
}
return &$cb($param) if ref $cb eq 'CODE';
}
if ($param eq '@CMD') {
# command wanted, but not found
croak "Command for $this->{name} not found" unless defined $this->{program};
return @CMD;
}
return $param;
};
# add command arguments
my @cmd;
for my $arg (@$args) {
local $_ = $arg;
# can't do arrays with s///
# this limits that @arg must be single argument
if (/@/) {
push(@cmd, $cb_->($_));
} else {
s/([\$]\w+)/$cb_->($1)/ge;
push(@cmd, $_);
}
}
my $op = shift @cmd;
my $fh;
if ($op eq '=' and ref $cb eq 'SCALAR') {
# Special: use open2
use IPC::Open2;
warn "DEBUG EXEC: $op @cmd" if $debug;
my $pid = open2($fh, $$cb, @cmd) or croak "open2 failed: @cmd: $!";
} elsif ($op eq '>&2') {
# Special: same as '|-' but reads both STDERR and STDOUT
use IPC::Open3;
warn "DEBUG EXEC: $op @cmd" if $debug;
my $pid = open3(undef, $fh, $cb, @cmd);
} else {
warn "DEBUG EXEC: @cmd" if $debug;
open($fh, $op, @cmd) or croak "open failed: @cmd: $!";
}
# for dir handles, reopen as opendir
if (-d $fh) {
undef($fh);
warn "DEBUG OPENDIR: $cmd[0]" if $debug;
opendir($fh, $cmd[0]) or croak "opendir failed: @cmd: $!";
}
return $fh;
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGIN
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/aaccli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AACCLI';
package App::Monitoring::Plugin::CheckRaid::Plugins::aaccli;
# Adaptec ServeRAID
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
shift->{name};
}
sub commands {
{
'container list' => ['=', '@CMD'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
"CHECK_RAID ALL=(root) NOPASSWD: $cmd container list /full"
}
sub check {
my $this = shift;
# status messages pushed here
my @status;
my $write = "";
$write .= "open aac0\n";
$write .= "container list /full\n";
$write .= "exit\n";
my $read = $this->cmd('container list', \$write);
#File foo receiving all output.
#
#AAC0>
#COMMAND: container list /full=TRUE
#Executing: container list /full=TRUE
#Num Total Oth Stripe Scsi Partition Creation
#Label Type Size Ctr Size Usage C:ID:L Offset:Size State RO Lk Task Done% Ent Date Time
#----- ------ ------ --- ------ ------- ------ ------------- ------- -- -- ------- ------ --- ------ --------
# 0 Mirror 74.5GB Open 0:02:0 64.0KB:74.5GB Normal 0 051006 13:48:54
# /dev/sda Auth 0:03:0 64.0KB:74.5GB Normal 1 051006 13:48:54
#
#
#AAC0>
#COMMAND: logfile end
#Executing: logfile end
while (<$read>) {
if (my($dsk, $stat) = /(\d:\d\d?:\d+)\s+\S+:\S+\s+(\S+)/) {
next unless $this->valid($dsk);
$dsk =~ s#:#/#g;
next unless $this->valid($dsk);
push(@status, "$dsk:$stat");
$this->critical if ($stat eq "Broken");
$this->warning if ($stat eq "Rebuild");
$this->warning if ($stat eq "Bld/Vfy");
$this->critical if ($stat eq "Missing");
if ($stat eq "Verify") {
$this->resync;
}
$this->warning if ($stat eq "VfyRepl");
}
}
close $read;
return unless @status;
$this->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AACCLI
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/afacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AFACLI';
package App::Monitoring::Plugin::CheckRaid::Plugins::afacli;
# Adaptec AACRAID
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
shift->{name};
}
sub commands {
{
'container list' => ['=', '@CMD'],
}
}
sub check {
my $this = shift;
# status messages pushed here
my @status;
my $write = "";
$write .= "open afa0\n";
$write .= "container list /full\n";
$write .= "exit\n";
my $read = $this->cmd('container list', \$write);
while (<$read>) {
# 0 Mirror 465GB Valid 0:00:0 64.0KB: 465GB Normal 0 032511 17:55:06
# /dev/sda root 0:01:0 64.0KB: 465GB Normal 1 032511 17:55:06
if (my($dsk, $stat) = /(\d:\d\d?:\d+)\s+\S+:\s?\S+\s+(\S+)/) {
next unless $this->valid($dsk);
$dsk =~ s#:#/#g;
next unless $this->valid($dsk);
push(@status, "$dsk:$stat");
$this->critical if ($stat eq "Broken");
$this->warning if ($stat eq "Rebuild");
$this->warning if ($stat eq "Bld/Vfy");
$this->critical if ($stat eq "Missing");
if ($stat eq "Verify") {
$this->resync;
}
$this->warning if ($stat eq "VfyRepl");
}
}
close $read;
return unless @status;
$this->ok->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_AFACLI
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/arcconf.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARCCONF';
package App::Monitoring::Plugin::CheckRaid::Plugins::arcconf;
# Adaptec AAC-RAID
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
shift->{name};
}
sub commands {
{
'getstatus' => ['-|', '@CMD', 'GETSTATUS', '1'],
# 'nologs' does not exist in arcconf 6.50. #118
'getconfig' => ['-|', '@CMD', 'GETCONFIG', '$ctrl', 'AL'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
(
"CHECK_RAID ALL=(root) NOPASSWD: $cmd GETSTATUS 1",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd GETCONFIG * AL",
);
}
sub parse_error {
my ($this, $message) = @_;
warn "arcconf: parse error: $message";
$this->unknown->message("Parse Error: $message");
}
# parse GETSTATUS command
# parses
# - number of controllers
# - logical device tasks (if any running)
sub parse_status {
my ($this) = @_;
my $count = 0;
my $ok = 0;
my $fh = $this->cmd('getstatus');
my %s;
# controller task
my %task;
while (<$fh>) {
chomp;
# empty line or comment
next if /^$/ or /^#/;
# termination
if (/^Command completed successfully/) {
$ok = 1;
last;
}
if (my($c) = /^Controllers [Ff]ound: (\d+)/) {
$count = int($c);
next;
}
if (/^(\S.+) Task:$/) {
$task{type} = $1;
next;
}
if (/^\s+Logical device\s+: (\d+)/) {
$task{device} = $1;
} elsif (/^\s+Task ID\s+: (\d+)/) {
$task{id} = $1;
} elsif (/^\s+Current operation\s+: (.+)/) {
$task{operation} = $1;
} elsif (/^\s+Status\s+: (.+)/) {
$task{status} = $1;
} elsif (/^\s+Priority\s+: (.+)/) {
$task{priority} = $1;
} elsif (/^\s+Percentage complete\s+: (\d+)/) {
$task{percent} = $1;
} elsif (/^Invalid controller number/) {
;
} else {
warn "Unknown line: [$_]";
# FIXME: ->message() gets overwritten later on
$this->unknown->message("Unknown line: [$_]");
}
}
close($fh);
# Tasks seem to be Controller specific, but as we don't support over one controller, let it be global
$s{tasks} = { %task } if %task;
if ($count == 0) {
# if command completed, but no controllers,
# assume no hardware present
if (!$ok) {
$this->unknown->message("No controllers found!");
}
return undef;
}
$s{ctrl_count} = $count;
return \%s;
}
# parse GETCONFIG for all controllers
sub parse_config {
my ($this, $status) = @_;
my %c;
for (my $i = 1; $i <= $status->{ctrl_count}; $i++) {
$c{$i} = $this->parse_ctrl_config($i, $status->{ctrl_count});
}
return { controllers => \%c };
}
# parse GETCONFIG command for specific controller
sub parse_ctrl_config {
my ($this, $ctrl, $ctrl_count) = @_;
# Controller information, Logical/Physical device info
my ($ld, $ch, $pd);
my $res = { controller => {}, logical => [], physical => [] };
my $fh = $this->cmd('getconfig', { '$ctrl' => $ctrl });
my ($section, $subsection, $ok);
my %sectiondata = ();
# called when data for section needs to be processed
my $flush = sub {
my $method = 'process_' . lc($section);
$method =~ s/[.\s]+/_/g;
$this->$method($res, \%sectiondata);
%sectiondata = ();
};
my $subsection_reset = sub {
$ch = 0;
undef($ld);
undef($pd);
undef($subsection);
};
while (<$fh>) {
chomp;
# empty line or comment
if (/^$/ or /^#/) {
&$subsection_reset;
next;
}
if (/^Command completed successfully/) {
$ok = 1;
last;
}
if (my($c) = /^Controllers [Ff]ound: (\d+)/) {
if ($c != $ctrl_count) {
# internal error?!
$this->unknown->message("Controller count mismatch");
}
next;
}
# section start
if (/^---+/) {
if (my($s) = <$fh> =~ /^(\w.+)$/) {
# flush the lines
if (defined($section)) {
&$flush();
}
$section = $s;
unless (<$fh> =~ /^---+/) {
$this->parse_error($_);
}
&$subsection_reset;
next;
}
$this->parse_error($_);
}
# sub section start
# there are also sections in subsections, but currently section names
# are unique enough
if (/^\s+---+/) {
if (my($s) = <$fh> =~ /^\s+(\S.+?)\s*?$/) {
$subsection = $s;
unless (<$fh> =~ /^\s+---+/) {
$this->parse_error($_);
}
next;
}
$this->parse_error($_);
}
warn("SKIP without section: [$_]\n"),next unless defined $section;
# regex notes:
# - value portion may be missing
# - value may be empty
# - value may be truncated (t/data/arcconf/issue47/getconfig)
my ($key, $value) = /^\s*(.+?)(?:\s+:\s*(.*?))?$/;
if ($section =~ /Controller [Ii]nformation/) {
if (not defined $subsection) {
$sectiondata{$key} = $value;
} else {
$sectiondata{$subsection}{$key} = $value;
}
} elsif ($section =~ /Physical Device [Ii]nformation/) {
if (my($c) = /Channel #(\d+)/) {
$ch = int($c);
undef($pd);
next;
} elsif (my($n) = /^\s+Device #(\d+)/) {
$pd = int($n);
next;
} else {
if (not defined $pd) {
$sectiondata{$ch}{$key} = $value;
} elsif (not defined $subsection) {
$sectiondata{$ch}{'pd'}{$pd}{$key} = $value;
} else {
$sectiondata{$ch}{'pd'}{$pd}{$subsection}{$key} = $value;
}
}
} elsif ($section =~ /Logical ([Dd]evice|drive) [Ii]nformation/) {
if (my($n) = /Logical (?:[Dd]evice|drive) [Nn]umber (\d+)/) {
$ld = int($n);
} else {
# skip lone line: issue87/getconfig
if (/No logical devices configured/) {
next;
}
if (not defined $ld) {
warn "LD undefined:[$_]\n";
next;
}
if (not defined $subsection) {
$sectiondata{$ld}{$key} = $value;
} else {
$sectiondata{$ld}{$subsection}{$key} = $value;
}
}
} elsif ($section eq 'MaxCache 3.0 information') {
# not parsed yet
} elsif ($section eq 'Connector information') {
# not parsed yet
} else {
warn "NOT PARSED: [$section] [$_]";
}
}
close $fh;
&$flush() if $section;
$this->unknown->message("Command did not succeed") unless defined $ok;
return $res;
}
# Process Controller Information section
sub process_controller_information {
my ($this, $res, $data) = @_;
my $c = {};
my $s;
# current section
my $cs = $data;
$c->{status} = $cs->{'Controller Status'};
if (exists $cs->{$s = 'Defunct Disk Drive Count'} || exists $cs->{$s = 'Defunct disk drive count'}) {
$c->{defunct_count} = int($cs->{$s});
}
if ($s = $cs->{'Logical devices/Failed/Degraded'}) {
my($td, $fd, $dd) = $s =~ m{(\d+)/(\d+)/(\d+)};
$c->{logical_count} = int($td);
$c->{logical_failed} = int($fd);
$c->{logical_degraded} = int($dd);
}
# ARCCONF 9.30: Logical drives/Offline/Critical
if ($s = $cs->{'Logical drives/Offline/Critical'}) {
my($td2, $fd2, $dd2) = $s =~ m{(\d+)/(\d+)/(\d+)};
$c->{logical_count} = int($td2);
$c->{logical_offline} = int($fd2);
$c->{logical_critical} = int($dd2);
}
$cs = $data->{'Controller Battery Information'};
$c->{battery_status} = $cs->{Status} if exists $cs->{Status};
$c->{battery_overtemp} = $cs->{'Over temperature'} if exists $cs->{'Over temperature'};
if ($s = $cs->{'Capacity remaining'}) {
my ($bc) = $s =~ m{(\d+)\s*percent.*$};
$c->{battery_capacity} = int($bc);
}
if ($s = $cs->{'Time remaining (at current draw)'}) {
my($d, $h, $m) = $s =~ /(\d+) days, (\d+) hours, (\d+) minutes/;
$c->{battery_time} = int($d) * 1440 + int($h) * 60 + int($m);
$c->{battery_time_full} = "${d}d${h}h${m}m";
}
$cs = $data->{'Controller ZMM Information'};
$c->{zmm_status} = $cs->{Status} if exists $cs->{'Status'};
$res->{controller} = $c;
}
sub process_logical_device_information {
my ($this, $res, $data) = @_;
my $s;
my @ld;
while (my($ld, $cs) = each %$data) {
$ld[$ld]{id} = $ld;
if (exists $cs->{$s = 'RAID Level'} || exists $cs->{$s = 'RAID level'}) {
$ld[$ld]{raid} = $cs->{$s};
}
$ld[$ld]{size} = $cs->{'Size'};
$ld[$ld]{failed_stripes} = $cs->{'Failed stripes'} if exists $cs->{'Failed stripes'};
$ld[$ld]{defunct_segments} = $cs->{'Defunct segments'} if exists $cs->{'Defunct segments'};
if ($s = $cs->{'Status of Logical Device'} || $cs->{'Status of logical device'} || $cs->{'Status of logical drive'}) {
$ld[$ld]{status} = $s;
}
if ($s = $cs->{'Logical Device name'} || $cs->{'Logical device name'} || $cs->{'Logical drive name'}) {
$ld[$ld]{name} = $s;
}
# Write-cache mode : Not supported]
# Partitioned : Yes]
# Number of segments : 2]
# Drive(s) (Channel,Device) : 0,0 0,1]
# Defunct segments : No]
}
$res->{logical} = \@ld;
}
sub process_physical_device_information {
my ($this, $res, $data) = @_;
# Keys with no values:
# "Device #0"
# "Device is a Hard drive"
#
# ignored:
# /Transfer Speed\s+:\s+(.+)/
# /Initiator at SCSI ID/
# /No physical drives attached/
my (@pd, $cs, $s);
while (my($ch, $channel_data) = each %$data) {
while (my($pd, $cs) = each %{$channel_data->{pd}}) {
$pd[$ch][$pd]{device_id} = $pd;
$pd[$ch][$pd]{power_state} = $cs->{'Power State'} if exists $cs->{'Power State'};
$pd[$ch][$pd]{status} = $cs->{'State'} if exists $cs->{'State'};
$pd[$ch][$pd]{supported} = $cs->{'Supported'} if exists $cs->{'Supported'};
$pd[$ch][$pd]{spare} = $cs->{'Dedicated Spare for'} if exists $cs->{'Dedicated Spare for'};
$pd[$ch][$pd]{model} = $cs->{'Model'};
$pd[$ch][$pd]{serial} = $cs->{'Serial number'} if exists $cs->{'Serial number'};
$pd[$ch][$pd]{wwn} = $cs->{'World-wide name'} if exists $cs->{'World-wide name'};
$pd[$ch][$pd]{write_cache} = $cs->{'Write Cache'} if exists $cs->{'Write Cache'};
$pd[$ch][$pd]{ssd} = $cs->{'SSD'} if exists $cs->{'SSD'};
$pd[$ch][$pd]{fru} = $cs->{'FRU'} if exists $cs->{'FRU'};
$pd[$ch][$pd]{ncq} = $cs->{'NCQ status'} if exists $cs->{'NCQ status'};
$pd[$ch][$pd]{pfa} = $cs->{'PFA'} if exists $cs->{'PFA'};
$pd[$ch][$pd]{enclosure} = $cs->{'Enclosure ID'} if exists $cs->{'Enclosure ID'};
$pd[$ch][$pd]{type} = $cs->{'Type'} if exists $cs->{'Type'};
$pd[$ch][$pd]{smart} = $cs->{'S.M.A.R.T.'} if exists $cs->{'S.M.A.R.T.'};
$pd[$ch][$pd]{smart_warn} = $cs->{'S.M.A.R.T. warnings'} if exists $cs->{'S.M.A.R.T. warnings'};
$pd[$ch][$pd]{speed} = $cs->{'Transfer Speed'} if $cs->{'Transfer Speed'};
$pd[$ch][$pd]{power_states} = $cs->{'Supported Power States'} if exists $cs->{'Supported Power States'};
$pd[$ch][$pd]{fail_ldev_segs} = $cs->{'Failed logical device segments'} if exists $cs->{'Failed logical device segments'};
# allow edits, i.e removed 'Vendor'/'Firmware' value from test data
$pd[$ch][$pd]{vendor} = $cs->{'Vendor'} || '';
$pd[$ch][$pd]{firmware} = $cs->{'Firmware'} if exists $cs->{'Firmware'};
# previous parser was not exact line match
if ($s = $cs->{'Size'} || $cs->{'Total Size'}) {
$pd[$ch][$pd]{size} = $s;
}
$s = $cs->{'Reported ESD'} || $cs->{'Reported ESD(T:L)'};
$pd[$ch][$pd]{esd} = $s if $s;
if ($s = $cs->{'Reported Location'}) {
my($e, $s) = $s =~ /(?:Enclosure|Connector) (\d+), (?:Slot|Device) (\d+)/;
$pd[$ch][$pd]{location} = "$e:$s";
}
if ($s = $cs->{'Reported Channel,Device'} || $cs->{'Reported Channel,Device(T:L)'}) {
$pd[$ch][$pd]{cd} = $s;
}
if (exists $cs->{$s = 'Device is a Hard drive'}
|| exists $cs->{$s = 'Device is an Enclosure'}
|| exists $cs->{$s = 'Device is an Enclosure services device'}
|| exists $cs->{$s = 'Device is an Enclosure Services Device'}
) {
($pd[$ch][$pd]{devtype}) = $s =~ /Device is an?\s+(.+)/;
}
# TODO: normalize and other formats:
# Current Temperature : 27 deg C
# Life-time Temperature Recorded
# Temperature : 51 C/ 123 F (Normal)
# Temperature : Normal
# Temperature : Not Supported
# Temperature Sensor Status 1 : 21 C/ 69 F (Normal)
# Temperature Sensor Status 1 : 23 C/ 73 F (Normal)
# Temperature Sensor Status 1 : 27 C/ 80 F (Normal)
# Temperature Sensor Status 1 : 46 C/ 114 F (Abnormal)
# Temperature status : Normal
# Threshold Temperature : 51 deg C
# FIXME: previous code used last line with /Temperature/ match
if ($s = $cs->{'Temperature'} || $cs->{'Temperature Sensor Status 1'} || $cs->{'Temperature status'}) {
$pd[$ch][$pd]{temperature} = $s;
}
# ignored:
# Status of Enclosure
# (Fan \d+|Speaker) status/
# /Expander ID\s+:/
# /Enclosure Logical Identifier\s+:/
# /Expander SAS Address\s+:/
# /[Mm]axCache (Capable|Assigned)\s+:\s+(.+)/
# /Power supply \d+ status/
}
}
$res->{physical} = \@pd;
}
sub process_logical_drive_information {
shift->process_logical_device_information(@_);
}
sub process_maxcache_3_0_information {
}
# TODO: issue152/arc2_getconfig.txt
sub process_connector_information {
}
# NB: side effect: ARCCONF changes current directory to /var/log
sub parse {
my ($this) = @_;
# we chdir to /var/log, as tool is creating 'UcliEvt.log'
# this can be disabled with 'nologs' parameter, but not sure do all versions support it
chdir('/var/log') || chdir('/');
my ($status, $config);
$status = $this->parse_status or return;
$config = $this->parse_config($status) or return;
return { %$status, %$config };
}
# check for controller status
sub check_controller {
my ($this, $c) = @_;
my @status;
$this->critical if $c->{status} !~ /Optimal|Okay|OK/;
push(@status, "Controller:$c->{status}");
if ($c->{defunct_count} > 0) {
$this->critical;
push(@status, "Defunct drives:$c->{defunct_count}");
}
if (defined $c->{logical_failed} && $c->{logical_failed} > 0) {
$this->critical;
push(@status, "Failed drives:$c->{logical_failed}");
}
if (defined $c->{logical_degraded} && $c->{logical_degraded} > 0) {
$this->critical;
push(@status, "Degraded drives:$c->{logical_degraded}");
}
if (defined $c->{logical_offline} && $c->{logical_offline} > 0) {
$this->critical;
push(@status, "Offline drives:$c->{logical_offline}");
}
if (defined $c->{logical_critical} && $c->{logical_critical} > 0) {
$this->critical;
push(@status, "Critical drives:$c->{logical_critical}");
}
# ZMM (Zero-Maintenance Module) status
if (defined($c->{zmm_status})) {
push(@status, "ZMM Status: $c->{zmm_status}");
}
# Battery status
if ($this->bbu_monitoring) {
my @s = $this->battery_status($c);
push(@status, @s) if @s;
}
return @status;
}
# check for physical devices
sub check_physical {
my ($this, $p) = @_;
my %pd;
$this->{pd_resync} = 0;
for my $ch (@$p) {
for my $pd (@{$ch}) {
# skip not disks
next if not defined $pd;
next if $pd->{devtype} =~ m/Enclosure/;
if ($pd->{status} eq 'Rebuilding') {
$this->resync;
$this->{pd_resync}++;
} elsif ($pd->{status} eq 'Dedicated Hot-Spare') {
$this->spare;
$pd->{status} = "$pd->{status} for $pd->{spare}";
} elsif ($pd->{status} !~ /^Online|Hot[- ]Spare|Ready/) {
$this->critical;
}
my $id = $pd->{serial} || $pd->{wwn} || $pd->{location} || $pd->{cd};
push(@{$pd{$pd->{status}}}, $id);
}
}
return \%pd;
}
# check for logical devices
sub check_logical {
my ($this, $l) = @_;
my @status;
for my $ld (@$l) {
next unless $ld; # FIXME: fix that script assumes controllers start from '0'
if ($ld->{status} eq 'Degraded' && $this->{pd_resync}) {
$this->warning;
} elsif ($ld->{status} !~ /Optimal|Okay/) {
$this->critical;
}
my $id = $ld->{id};
if ($ld->{name}) {
$id = "$id($ld->{name})";
}
push(@status, "Logical Device $id:$ld->{status}");
if (defined $ld->{failed_stripes} && $ld->{failed_stripes} ne 'No') {
push(@status, "Failed stripes: $ld->{failed_stripes}");
}
if (defined $ld->{defunct_segments} && $ld->{defunct_segments} ne 'No') {
push(@status, "Defunct segments: $ld->{defunct_segments}");
}
}
return @status;
}
sub check {
my $this = shift;
my $data = $this->parse;
$this->unknown,return unless $data;
my @status;
for my $i (sort {$a cmp $b} keys %{$data->{controllers}}) {
my $c = $data->{controllers}->{$i};
push(@status, $this->check_controller($c->{controller}));
# current (logical device) tasks
if ($data->{tasks}->{operation} ne 'None') {
# just print it. no status change
my $task = $data->{tasks};
push(@status, "$task->{type} #$task->{device}: $task->{operation}: $task->{status} $task->{percent}%");
}
# check physical first, as it setups pd_resync flag
my $pd = $this->check_physical($c->{physical});
push(@status, $this->check_logical($c->{logical}));
# but report after logical devices
push(@status, "Drives: ".$this->join_status($pd)) if $pd;
}
$this->ok->message(join(', ', @status));
}
# check battery status in $c
sub battery_status {
my ($this, $c) = @_;
my @status;
if (!defined($c->{battery_status}) || $c->{battery_status} eq 'Not Installed') {
return;
}
push(@status, "Battery Status: $c->{battery_status}");
# if battery status is 'Failed', none of the details below are available. #105
if ($c->{battery_status} eq 'Failed') {
$this->critical;
return @status;
}
# detailed battery checks
if ($c->{battery_overtemp} ne 'No') {
$this->critical;
push(@status, "Battery Overtemp: $c->{battery_overtemp}");
}
push(@status, "Battery Capacity Remaining: $c->{battery_capacity}%");
if ($c->{battery_capacity} < 50) {
$this->critical;
}
if ($c->{battery_capacity} < 25) {
$this->warning;
}
if ($c->{battery_time} < 1440) {
$this->warning;
}
if ($c->{battery_time} < 720) {
$this->critical;
}
if ($c->{battery_time} < 60) {
push(@status, "Battery Time: $c->{battery_time}m");
} else {
push(@status, "Battery Time: $c->{battery_time_full}");
}
return @status;
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARCCONF
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/areca.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARECA';
package App::Monitoring::Plugin::CheckRaid::Plugins::areca;
## Areca SATA RAID Support
## requires cli64 or cli32 binaries
## For links to manuals and binaries, see this issue:
## https://github.com/glensc/nagios-plugin-check_raid/issues/10
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
qw(areca-cli areca_cli64 areca_cli32 cli64 cli32);
}
sub commands {
{
'rsf info' => ['-|', '@CMD', 'rsf', 'info'],
'disk info' => ['-|', '@CMD', 'disk', 'info'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
(
"CHECK_RAID ALL=(root) NOPASSWD: $cmd rsf info",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd disk info",
);
}
# plugin check
# can store its exit code in $this->status
# can output its message in $this->message
sub check {
my $this = shift;
## Check Array Status
my (@status, %arrays);
my $fh = $this->cmd('rsf info');
while (<$fh>) {
=cut
# Name Disks TotalCap FreeCap MinDiskCap State
# Name Disks TotalCap FreeCap DiskChannels State
===============================================================================
1 Raid Set # 000 23 34500.0GB 0.0GB 1500.0GB Normal
1 Raid Set # 00 15 15000.0GB 0.0GB 123G567C9AB48EF Normal
1 data 15 11250.0GB 0.0GB 123456789ABCDEF Normal
1 data 15 11250.0GB 0.0GB 123456789ABCDEF Initializing
===============================================================================
=cut
next unless (my($id, $n, $s) = m{^
\s*(\d+) # Id
\s+(.+) # Name
\s+\d+ # Disks
\s+\S+ # TotalCap
\s+\S+ # FreeCap
\s+\S+ # MinDiskCap/DiskChannels
\s+(\S+)\s* # State
$}x);
# trim trailing spaces from name
$n =~ s/\s+$//;
if ($s =~ /[Rr]e[Bb]uild/) {
$this->warning;
} elsif ($s !~ /[Nn]ormal|[Rr]e[Bb]uild|Checking|Initializing/) {
$this->critical;
}
push(@status, "Array#$id($n): $s");
$arrays{$n} = [ $id, $s ];
}
close $fh;
## Check Drive Status
$fh = $this->cmd('disk info');
my %drivestatus;
while (<$fh>) {
chomp;
=cut
# Enc# Slot# ModelName Capacity Usage
===============================================================================
1 01 Slot#1 N.A. 0.0GB N.A.
8 01 Slot#8 N.A. 0.0GB N.A.
9 02 SLOT 01 ST31500341AS 1500.3GB Raid Set # 000
11 02 SLOT 03 ST31500341AS 1500.3GB Raid Set # 000
# Ch# ModelName Capacity Usage
===============================================================================
1 1 ST31000340NS 1000.2GB Raid Set # 00
6 6 ST31000340NS 1000.2GB Raid Set # 00
3 3 WDC WD7500AYYS-01RCA0 750.2GB data
4 4 WDC WD7500AYYS-01RCA0 750.2GB data
16 16 WDC WD7500AYYS-01RCA0 750.2GB HotSpare[Global]
=cut
next unless my($id, $model, $usage) = m{^
\s*(\d+) # Id
\s+\d+ # Channel/Enclosure (not reliable, tests 1,2,12 differ)
\s+(.+) # ModelName
\s+\d+.\d\S+ # Capacity
\s+(.+) # Usage (Raid Name)
}x;
# trim trailing spaces from name
$usage =~ s/\s+$//;
# Asssume model N.A. means the slot not in use
# we could also check for Capacity being zero, but this seems more
# reliable.
next if $usage eq 'N.A.';
# use array id in output: shorter
my $array_id = defined($arrays{$usage}) ? ($arrays{$usage})->[0] : undef;
my $array_name = defined $array_id ? "Array#$array_id" : $usage;
# assume critical if Usage is not one of:
# - existing Array name
# - HotSpare
# - Rebuild
if (defined($arrays{$usage})) {
# Disk in Array named $usage
push(@{$drivestatus{$array_name}}, $id);
} elsif ($usage =~ /[Rr]e[Bb]uild/) {
# rebuild marks warning
push(@{$drivestatus{$array_name}}, $id);
$this->warning;
} elsif ($usage =~ /HotSpare/) {
# hotspare is OK
push(@{$drivestatus{$array_name}}, $id);
} elsif ($usage =~ /Pass Through/) {
# Pass Through is OK
push(@{$drivestatus{$array_name}}, $id);
} else {
push(@{$drivestatus{$array_name}}, $id);
$this->critical;
}
}
close $fh;
push(@status, "Drive Assignment: ".$this->join_status(\%drivestatus)) if %drivestatus;
$this->ok->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_ARECA
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/cciss.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CCISS';
package App::Monitoring::Plugin::CheckRaid::Plugins::cciss;
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use App::Monitoring::Plugin::CheckRaid::Plugins::lsscsi;
use App::Monitoring::Plugin::CheckRaid::Plugins::smartctl;
use strict;
use warnings;
sub program_names {
'cciss_vol_status';
}
sub commands {
{
'controller status' => ['-|', '@CMD', '@devs'],
'controller status verbose' => ['-|', '@CMD', '-V', '@devs'],
'cciss_vol_status version' => ['>&2', '@CMD', '-v'],
'detect hpsa' => ['<', '/sys/module/hpsa/refcnt'],
'detect cciss' => ['<', '/proc/driver/cciss'],
'cciss proc' => ['<', '/proc/driver/cciss/$controller'],
# for lsscsi, issue #109
'lsscsi list' => ['-|', '@CMD', '-g'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
my $v1_10 = $this->cciss_vol_status_version >= 1.10;
my @sudo;
my @cciss_devs = $this->detect;
if (@cciss_devs) {
my $c = join(' ', @cciss_devs);
if ($v1_10) {
push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cmd -V $c");
} else {
push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cmd $c");
}
}
my @cciss_disks = $this->detect_disks(@cciss_devs);
if (!$v1_10 && @cciss_disks) {
my $smartctl = App::Monitoring::Plugin::CheckRaid::Plugins::smartctl->new();
if ($smartctl->active) {
my $cmd = $smartctl->{program};
foreach my $ref (@cciss_disks) {
my ($dev, $diskopt, $disk) = @$ref;
# escape comma for sudo
$diskopt =~ s/,/\\$&/g;
push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cmd -H $dev $diskopt$disk");
}
}
}
return @sudo;
}
# detects if hpsa (formerly cciss) is present in system
sub detect {
my $this = shift;
my ($fh, @devs);
# try lsscsi first if enabled and allowed
my $lsscsi = App::Monitoring::Plugin::CheckRaid::Plugins::lsscsi->new('commands' => $this->{commands});
my $use_lsscsi = defined($this->{use_lsscsi}) ? $this->{use_lsscsi} : $lsscsi->active;
if ($use_lsscsi) {
# for cciss_vol_status < 1.10 we need /dev/sgX nodes, columns which are type storage
@devs = $lsscsi->list_sg;
# cciss_vol_status 1.10 can process disk nodes too even if sg is not present
my $v1_10 = $this->cciss_vol_status_version >= 1.10;
if (!@devs && $v1_10) {
@devs = $lsscsi->list_dd;
}
return wantarray ? @devs : \@devs if @devs;
}
# check hpsa devs
eval { $fh = $this->cmd('detect hpsa'); };
if ($fh) {
my $refcnt = <$fh>;
close $fh;
if ($refcnt) {
# TODO: how to figure which sgX is actually in use?
# for now we collect all, and expect cciss_vol_status to ignore unknowns
# refcnt seems to match number of sg devs: /sys/class/scsi_generic/sg*
for (my $i = 0; $i < $refcnt; $i++) {
my $dev = "/dev/sg$i";
# filter via valid() so could exclude devs
push(@devs, $dev) if $this->valid($dev);
}
}
}
undef($fh);
# check legacy cciss devs
eval { $fh = $this->cmd('detect cciss'); };
if ($fh) {
my @c = grep { !/^\./ } readdir($fh);
close($fh);
# find controllers
# cciss0: HP Smart Array P400i Controller
# Board ID: 0x3235103c
# Firmware Version: 4.06
# IRQ: 98
# Logical drives: 1
# Current Q depth: 0
# Current # commands on controller: 0
# Max Q depth since init: 249
# Max # commands on controller since init: 275
# Max SG entries since init: 31
# Sequential access devices: 0
#
# cciss/c0d0: 220.12GB RAID 1(1+0)
for my $c (@c) {
my $fh = $this->cmd('cciss proc', { '$controller' => $c });
while (<$fh>) {
# check "c*d0" - iterate over each controller
next unless (my($dev) = m{^(cciss/c\d+d0):});
$dev = "/dev/$dev";
# filter via valid() so could exclude devs
push(@devs, $dev) if $this->valid($dev);
}
close $fh;
}
}
undef($fh);
return wantarray ? @devs : \@devs;
}
# build list of cciss disks
# used by smartctl check
# just return all disks (0..15) for each cciss dev found
sub detect_disks {
my $this = shift;
my @devs;
# build devices list for smartctl
foreach my $scsi_dev (@_) {
foreach my $disk (0..15) {
push(@devs, [ $scsi_dev, '-dcciss,', $disk ]);
}
}
return wantarray ? @devs : \@devs;
}
# parse version out of "cciss_vol_status version 1.09"
# NOTE: it prints the output to stderr, but may print to stdout in the future
sub cciss_vol_status_version {
my $this = shift;
# cache inside single run
return $this->{cciss_vol_status_version} if defined $this->{cciss_vol_status_version};
my $version = sub {
my $fh = $this->nosudo_cmd('cciss_vol_status version');
my ($line) = <$fh>;
close $fh;
return 0 unless $line;
if (my($v) = $line =~ /^cciss_vol_status version ([\d.]+)$/) {
return 0 + $v;
}
return 0;
};
return $this->{cciss_vol_status_version} = &$version();
}
sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
# we process until we find end of sentence (dot at the end of the line)
sub consume_diagnostic {
my ($this, $fh) = @_;
my $diagnostic = '';
while (1) {
my $s = <$fh>;
last unless $s;
chomp;
$diagnostic .= ' '. trim($s);
last if $s =~ /\.$/;
}
return trim($diagnostic);
}
# process to skip lines with physical location:
# " connector 1I box 1 bay 4 ..."
sub consume_disk_map {
my ($this, $fh) = @_;
while (my $s = <$fh>) {
chomp $s;
# connector 1I box 1 bay 4
last unless $s =~ /^\s+connector\s/;
}
}
sub parse {
my $this = shift;
my @devs = @_;
my (%c, $cdev);
# cciss_vol_status 1.10 has -V option to print more info about controller and disks.
my $v1_10 = $this->cciss_vol_status_version >= 1.10;
# add all devs at once to commandline, cciss_vol_status can do that
my $fh = $this->cmd($v1_10 ? 'controller status verbose' : 'controller status', { '@devs' => \@devs });
while (<$fh>) {
chomp;
# skip empty lines and artificial comments (added by this project)
next if /^$/ or /^#/;
if (/Controller:/) {
# this is first item when new controller is found
# reset previous state
undef $cdev;
next;
}
# catch enclosures, print_bus_status()
# /dev/cciss/c1d0: (Smart Array P800) Enclosure MSA70 (S/N: SGA651004J) on Bus 2, Physical Port 1E status: OK.
# /dev/cciss/c0d0: (Smart Array 6i) Enclosure PROLIANT 6L2I (S/N: ) on Bus 0, Physical Port J1 status: OK.
if (my($file, $board_name, $name, $sn, $bus, $port1, $port2, $status) = m{
^(/dev/[^:]+):\s # File
\(([^)]+)\)\s # Board Name
Enclosure\s(.*?)\s # Enclosure Name
\(S/N:\s(\S*)\)\s # Enclosure SN
on\sBus\s(\d+),\s # Bus Number
Physical\sPort\s(.) # physical_port1
(.)\s # physical_port2
status:\s(.*?)\. # status (without a dot)
}x) {
$c{$file}{enclosures}{$bus} = {
board_name => $board_name,
name => $name,
sn => $sn,
bus => int($bus),
phys1 => $port1,
phys2 => $port2,
status => $status,
};
next;
}
# volume status, print_volume_status()
# /dev/cciss/c0d0: (Smart Array P400i) RAID 1 Volume 0 status: OK
# /dev/sda: (Smart Array P410i) RAID 1 Volume 0 status: OK.
# /dev/sda: (Smart Array P410i) RAID 5 Volume 0 status: OK. At least one spare drive designated. At least one spare drive has failed.
if (my($file, $board_name, $raid_level, $volume_number, $certain, $status, $spare_drive_status) = m{
^(/dev/[^:]+):\s # File
\(([^)]+)\)\s # Board Name
(RAID\s\d+|\([^)]+\))\s # RAID level
Volume\s(\d+) # Volume number
(\(\?\))?\s # certain?
status:\s(.*?)\. # status (without a dot)
(.*)? # spare drive status messages
}x) {
$cdev = $file;
$c{$file}{volumes}{$volume_number} = {
board_name => $board_name,
raid_level => $raid_level,
volume_number => $volume_number,
certain => int(not defined $certain),
status => $status,
spare_drive_status => trim($spare_drive_status),
};
$c{$file}{board_name} = $board_name;
next;
}
next unless $cdev;
if (my ($count) = /Physical drives: (\d+)/) {
$c{$cdev}{'pd count'} = $count;
next;
}
# check_physical_drives(file, fd);
# NOTE: check for physical drives is enabled with -V or -s option (-V enables -s)
# cciss_vol_status.c format_phys_drive_location()
if (my ($phys1, $phys2, $box, $bay, $model, $serial_no, $fw_rev, $status) = m{
\sconnector\s(.)(.)\s # Phys connector 1&2
box\s(\d+)\s # phys_box_on_bus
bay\s(\d+)\s # phys_bay_in_box
(.{40})\s # model
(.{40})\s # serial no
(.{8})\s # fw rev
(.+) # status
$}x) {
my $slot = "$phys1$phys2-$box-$bay";
$c{$cdev}{drives}{$slot} = {
'slot' => $slot,
'phys1' => $phys1,
'phys2' => $phys2,
'box' => int($box),
'bay' => int($bay),
'model' => trim($model),
'serial' => trim($serial_no),
'fw' => trim($fw_rev),
'status' => $status,
};
next;
}
# TODO
# check_fan_power_temp(file, ctlrtype, fd, num_controllers);
# check_nonvolatile_cache_status(file, ctlrtype, fd, num_controllers);
# /dev/cciss/c0d0(Smart Array P400i:0): Non-Volatile Cache status:
if (my($file, $board_name, $instance) = m{^(/dev/[^(]+)\((.+):(\d+)\): Non-Volatile Cache status}) {
# $file and $dev may differ, so store it
$c{$cdev}{cache} = {
'file' => $file,
'board' => $board_name,
'instance' => int($instance),
};
next;
}
if (defined($c{$cdev}{cache})) {
my $cache = $c{$cdev}{cache};
my %map = (
configured => qr/Cache configured: (.+)/,
read_cache_memory => qr/Read cache memory: (.+)/,
write_cache_memory => qr/Write cache memory: (.+)/,
write_cache_enabled => qr/Write cache enabled: (.+)/,
flash_cache => qr/Flash backed cache present/,
disabled_temporarily => qr/Write cache temporarily disabled/,
disabled_permanently => qr/Write Cache permanently disabled/,
);
my $got;
while (my($k, $r) = each %map) {
next unless (my($v) = $_ =~ $r);
$cache->{$k} = $v;
$got = 1;
# consume extended diagnostic
if ($k =~ /disabled_(temporari|permanentl)ly/) {
$cache->{"$k diagnostic"} = $this->consume_diagnostic($fh);
}
}
next if $got;
}
# show_disk_map(" Failed drives:", file, fd, id, controller_lun, ctlrtype,
# show_disk_map(" 'Replacement' drives:", file, fd, id, controller_lun, ctlrtype,
# show_disk_map(" Drives currently substituted for by spares:", file, fd, id, controller_lun, ctlrtype,
if (/^ Failed drives:/ ||
/^ 'Replacement' drives:/ ||
/^ Drives currently substituted for by spares:/
) {
# could store this somewhere, ignore for now
$this->consume_disk_map($fh);
next;
}
if (my($total_failed) = /Total of (\d+) failed physical drives detected on this logical drive\./) {
$c{$cdev}{phys_failed} = $total_failed;
next;
}
warn "Unparsed[$_]";
}
close($fh);
return \%c;
}
sub check {
my $this = shift;
my @devs = $this->detect;
unless (@devs) {
$this->warning;
$this->message("No Smart Array Adapters were found on this machine");
return;
}
# status messages pushed here
my @status;
my $res = $this->parse(@devs);
for my $dev (sort {$a cmp $b} keys %$res) {
my $c = $res->{$dev};
my @bstatus;
# check volumes
my @vstatus;
for my $vn (sort {$a cmp $b} keys %{$c->{volumes}}) {
my $v = $c->{volumes}->{$vn};
if ($v->{status} !~ '^OK') {
$this->critical;
}
push(@vstatus, "Volume $v->{volume_number} ($v->{raid_level}): $v->{status}");
}
push(@bstatus, @vstatus) if @vstatus;
# check physical devices
if ($c->{'pd count'}) {
my %pd;
for my $ps (sort {$a cmp $b} keys %{$c->{drives}}) {
my $pd = $c->{drives}{$ps};
if ($pd->{status} !~ '^OK') {
$this->critical;
$ps .= "($pd->{serial})";
}
push(@{$pd{$pd->{status}}}, $ps);
}
push(@bstatus, "Drives($c->{'pd count'}): ". $this->join_status(\%pd));
}
# check enclosures
if ($c->{enclosures}) {
my @e;
for my $i (sort {$a cmp $b} keys %{$c->{enclosures}}) {
my $e = $c->{enclosures}{$i};
# enclosure name may be missing, identify by connection
my $s = $e->{name} || "$e->{bus}-$e->{phys1}$e->{phys2}";
# enclosure S/N may be missing
$s .= "($e->{sn})" if $e->{sn};
$s .= ": $e->{status}";
if ($e->{status} !~ '^OK') {
$this->critical;
}
push(@e, $s);
}
push(@bstatus, "Enclosures: ". join(', ', @e));
}
# check cache
if ($c->{cache} && $c->{cache}->{configured} eq 'Yes') {
my $cache = $c->{cache};
my @cstatus = 'Cache:';
if ($cache->{write_cache_enabled} eq 'Yes') {
push(@cstatus, "WriteCache");
} elsif ($cache->{disabled_temporarily} || $cache->{disabled_permanently}) {
# disabled diagnostic is available, but it's too long to print here
push(@cstatus, "WriteCache:DISABLED");
$this->cache_fail;
}
push(@cstatus, "FlashCache") if $cache->{flash_cache};
push(@cstatus, "ReadMem:$cache->{read_cache_memory}") if $cache->{read_cache_memory};
push(@cstatus, "WriteMem:$cache->{write_cache_memory}") if $cache->{write_cache_memory};
push(@bstatus, join(' ', @cstatus));
}
push(@status, "$dev($c->{board_name}): ". join(', ', @bstatus));
}
unless (@status) {
return;
}
# denote this plugin as ran ok
$this->ok;
$this->message(join(', ', @status));
# cciss_vol_status 1.10 with -V (or -s) checks individual disk health anyway
my $v1_10 = $this->cciss_vol_status_version >= 1.10;
# no_smartctl: allow skip from tests
if (!$v1_10 && !$this->{no_smartctl}) {
# check also individual disk health
my @disks = $this->detect_disks(@devs);
if (@disks) {
# inherit smartctl command from our commands (testing)
my %params = ();
$params{commands}{smartctl} = $this->{commands}{smartctl} if $this->{commands}{smartctl};
my $smartctl = App::Monitoring::Plugin::CheckRaid::Plugins::smartctl->new(%params);
# do not perform check if smartctl is missing
if ($smartctl->active) {
$smartctl->check_devices(@disks);
# XXX this is hack, as we have no proper subcommand check support
$this->message($this->message . " " .$smartctl->message);
if ($smartctl->status > 0) {
$this->critical;
}
}
}
}
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CCISS
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/cmdtool2.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CMDTOOL2';
package App::Monitoring::Plugin::CheckRaid::Plugins::cmdtool2;
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
'CmdTool2';
}
sub commands {
{
'adapter list' => ['-|', '@CMD', , '-AdpAllInfo', '-aALL', '-nolog'],
'adapter config' => ['-|', '@CMD', '-CfgDsply', '-a$adapter', '-nolog'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
(
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -AdpAllInfo -aALL -nolog",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -CfgDsply -a* -nolog",
);
}
sub check {
my $this = shift;
# status messages pushed here
my @status;
# get adapters
my $fh = $this->cmd('adapter list');
my @c;
while (<$fh>) {
if (my($c) = /^Adapter #(\d+)/) {
push(@c, $c);
}
}
close $fh;
unless (@c) {
$this->warning;
$this->message("No LSI adapters were found on this machine");
return;
}
foreach my $c (@c) {
my $fh = $this->cmd('adapter config', { '$adapter' => $c });
my ($d);
while (<$fh>) {
# DISK GROUPS: 0
if (my($s) = /^DISK GROUPS: (\d+)/) {
$d = int($s);
next;
}
# State: Optimal
if (my($s) = /^State: (\S+)$/) {
if ($s ne 'Optimal') {
$this->critical;
}
push(@status, "Logical Drive $c,$d: $s");
}
}
}
return unless @status;
# denote this plugin as ran ok
$this->ok;
$this->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_CMDTOOL2
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/dm.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DM';
package App::Monitoring::Plugin::CheckRaid::Plugins::dm;
# Package to check Linux Device Mapper
# Linux LVM Mirrors
# https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/mirror_create.html
#
# Linux LVM RAID
# https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/raid_volumes.html
#
# Low-level:
# https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/device_mapper.html#mirror-map
# https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/device_mapper.html#dmraid-map
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
qw(dmsetup);
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
(
"CHECK_RAID ALL=(root) NOPASSWD: $cmd status --noflush",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd status",
);
}
sub commands {
{
'dmsetup' => [ '-|', '@CMD', 'status' ],
'dmsetup noflush' => [ '-|', '@CMD', 'status', '--noflush' ],
}
}
# https://www.kernel.org/doc/Documentation/device-mapper/dm-raid.txt
sub parse_raid {
local $_ = shift;
# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1377
# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1409-L1423
# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1425-L1435
# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1437-L1442
# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1444-L1452
my @cols = qw(
raid_type raid_disks
status_chars
sync_ratio
sync_action
mismatch_cnt
);
my %h;
@h{@cols} = split;
\%h;
}
# https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/device_mapper.html#mirror-map
sub parse_mirror {
local $_ = shift;
my %h;
# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1355
my @parts = split;
# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1365
$h{nr_mirrors} = shift @parts;
# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1366-L1369
my @devs;
for (my $i = 0; $i < $h{nr_mirrors}; $i++) {
push(@devs, shift @parts);
}
$h{devices} = \@devs;
# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1372-L1374
# some ratio?
$h{ratio} = shift @parts;
# param count? always '1'
shift @parts;
# the 'buffer' filled with status chars
$h{status_chars} = shift @parts;
# log device information
# https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-log.c#L807-L810
# log params, always '3'
shift @parts;
my %l;
$l{type} = shift @parts;
$l{device} = shift @parts;
# status: F->D->A
$l{status_char} = shift @parts;
$h{log} = { %l };
# for debugging. fill only if something remains not parsed
$h{_remaining} = join ' ', @parts if @parts;
\%h;
}
sub parse_target {
my ($target, $data) = @_;
return parse_raid($data) if $target eq 'raid';
return parse_mirror($data) if $target eq 'mirror';
undef;
}
sub get_fh {
my $this = shift;
# use dmsetup --noflush, requires LVM >= 2.02.97
# if that fails, fall back to just dmsetup
# https://github.com/glensc/nagios-plugin-check_raid/issues/130#issuecomment-194476070
my $fh = $this->cmd('dmsetup noflush');
$fh = $this->cmd('dmsetup') if eof $fh;
return $fh;
}
sub parse {
my $this = shift;
my @devices;
my $fh = $this->get_fh();
while (<$fh>) {
# skip comments.
# not present in dmsetup output, but our test files may have.
next if /^#/;
last if /No devices found/;
if (my ($dmname, $s, $l, $target, $rest) = m{^
(\S+):\s+ # dmname
(\d+)\s+ # start
(\d+)\s+ # length
(\S+) # target
(?:\s+(.+))? # rest of the data
\s? # there may be trailing space
$}x) {
my $h = parse_target($target, $rest);
# skip target type not handled
next unless $h;
my %h = (
'dmname' => $dmname,
's' => $s,
'l' => $l,
'target' => $target,
%$h,
);
push @devices, \%h;
next;
}
warn "Unhandled:[$_]";
$this->unknown;
}
close $fh;
return \@devices;
}
sub check {
my $this = shift;
my $c = $this->parse;
if (!@$c) {
$this->noraid->message("No devices to check");
return;
}
my @status;
foreach my $dm (@$c)
{
# <status_chars> One char for each device, indicating:
# 'A' = alive and in-sync (mirror, raid1, raid)
# 'a' = alive but not in-sync (mirror, raid1)
# 'D' = dead/failed (mirror, raid1, raid)
# 'S' = Sync (mirror, raid1)
# 'mirror'/'raid1': https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid1.c#L1330-L1342
# 'raid': https://github.com/torvalds/linux/blob/v3.18/drivers/md/dm-raid.c#L1409-L1414
$this->critical if ($dm->{status_chars} =~ /D/);
$this->warning if ($dm->{status_chars} =~ /[aS]/);
my @s = "$dm->{dmname}:$dm->{status_chars}";
# <sync_action> One of the following possible states:
# idle - No synchronization action is being performed.
# frozen - The current action has been halted.
# resync - Array is undergoing its initial synchronization or...
# recover - A device in the array is being rebuilt or...
# check - A user-initiated full check of the array is...
# repair - The same as "check", but discrepancies are...
# reshape - The array is undergoing a reshape.
if ($dm->{sync_action}) {
push(@s, $dm->{sync_action});
if ($dm->{sync_action} =~ /^(check|repair|init)$/) {
$this->warning;
}
}
push(@status, join(' ', @s));
}
return unless @status;
$this->ok->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DM
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/dmraid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DMRAID';
package App::Monitoring::Plugin::CheckRaid::Plugins::dmraid;
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
shift->{name};
}
sub commands {
{
'dmraid' => ['-|', '@CMD', '-r'],
}
}
sub active {
my ($this) = @_;
# allow --plugin-option=dmraid-enabled to force this plugin to be enabled
return 1 if exists $this->{options}{'dmraid-enabled'};
# return if parent said NO
my $res = $this->SUPER::active(@_);
return $res unless $res;
# check if dmraid is empty
return keys %{$this->parse} > 0;
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -r";
}
# parse arrays, return data indexed by array name
sub parse {
my $this = shift;
my (%arrays);
my $fh = $this->cmd('dmraid');
while (<$fh>) {
chomp;
next unless (my($device, $format, $name, $type, $status, $sectors) = m{^
# /dev/sda: jmicron, "jmicron_JRAID", mirror, ok, 781385728 sectors, data@ 0
# /dev/sdb: ddf1, ".ddf1_disks", GROUP, ok, 1953253376 sectors, data@ 0
(/dev/\S+):\s # device
(\S+),\s # format
"([^"]+)",\s # name
(mirror|stripe[d]?|GROUP),\s # type
(\w+),\s # status
(\d+)\ssectors,.* # sectors
$}x);
next unless $this->valid($device);
# trim trailing spaces from name
$name =~ s/\s+$//;
my $member = {
'device' => $device,
'format' => $format,
'type' => $type,
'status' => $status,
'size' => $sectors,
};
push(@{$arrays{$name}}, $member);
}
close $fh;
return \%arrays;
}
# plugin check
# can store its exit code in $this->status
# can output its message in $this->message
sub check {
my $this = shift;
my (@status);
## Check Array and Drive Status
my $arrays = $this->parse;
while (my($name, $array) = each(%$arrays)) {
my @s;
foreach my $dev (@$array) {
if ($dev->{status} =~ m/sync|rebuild/i) {
$this->warning;
} elsif ($dev->{status} !~ m/ok/i) {
$this->critical;
}
my $size = $this->format_bytes($dev->{size});
push(@s, "$dev->{device}($dev->{type}, $size): $dev->{status}");
}
push(@status, "$name: " . join(', ', @s));
}
return unless @status;
# denote that this plugin as ran ok, not died unexpectedly
$this->ok->message(join(' ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DMRAID
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/dpt_i2o.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DPT_I2O';
package App::Monitoring::Plugin::CheckRaid::Plugins::dpt_i2o;
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub commands {
{
'proc' => ['<', '/proc/scsi/dpt_i2o'],
'proc entry' => ['<', '/proc/scsi/dpt_i2o/$controller'],
}
}
sub active {
my ($this) = @_;
return -d $this->{commands}{proc}[1];
}
sub check {
my $this = shift;
# status messages pushed here
my @status;
my $fh = $this->cmd('proc');
my @c = grep { !/^\./ } readdir($fh);
close($fh);
# TODO: check for failed disks!
for my $c (@c) {
my $fh = $this->cmd('proc entry', { '$controller' => $c });
while (<$fh>) {
if (my ($c, $t, $l, $s) = m/TID=\d+,\s+\(Channel=(\d+),\s+Target=(\d+),\s+Lun=(\d+)\)\s+\((\S+)\)/) {
if ($s ne "online") {
$this->critical;
}
push(@status, "$c,$t,$l:$s");
}
}
close($fh);
}
return unless @status;
# denote this plugin as ran ok
$this->ok;
$this->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_DPT_I2O
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/gdth.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_GDTH';
package App::Monitoring::Plugin::CheckRaid::Plugins::gdth;
# Linux gdth RAID
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub commands {
{
'proc' => ['<', '/proc/scsi/gdth'],
'proc entry' => ['<', '/proc/scsi/gdth/$controller'],
}
}
sub active {
my ($this) = @_;
return -d $this->{commands}{proc}[1];
}
sub parse {
my $this = shift;
my $fh = $this->cmd('proc');
my @c = grep { !/^\./ } readdir($fh);
close($fh);
my %c;
for my $c (@c) {
my (%ld, %ad, %pd, %l, %a, %p, $section);
my $fh = $this->cmd('proc entry', { '$controller' => $c });
while (<$fh>) {
chomp;
# new section start
if (my($s) = /^(\w.+):$/) {
$section = $s;
%a = %l = %p = ();
next;
}
# skip unknown sections
next unless /^\s/ or /^$/;
# process each section
if ($section eq 'Driver Parameters') {
# nothing useful
} elsif ($section eq 'Disk Array Controller Information') {
# nothing useful
} elsif ($section eq 'Physical Devices') {
# Chn/ID/LUN: B/05/0 Name: FUJITSU MAX3147NC 0104
# Capacity [MB]: 140239 To Log. Drive: 5
# Retries: 1 Reassigns: 0
# Grown Defects: 1
if (my($id, $n, $rv) = m{^\s+Chn/ID/LUN:\s+(\S+)\s+Name:\s+(.+)(.{4})$}) {
$n =~ s/\s+$//;
$p{id} = $id;
$p{name} = $n;
$p{revision} = $rv;
} elsif (my($unit, $c, $d) = m/^\s+Capacity\s\[(.B)\]:\s+(\d+)\s+To Log\. Drive:\s+(\d+|--)/) {
$p{capacity} = int($c);
$p{capacity_unit} = $unit;
$p{drive} = $d;
} elsif (my($r, $ra) = m/^\s+Retries:\s+(\d+)\s+Reassigns:\s+(\d+)/) {
$p{retries} = int($r);
$p{reassigns} = int($ra);
} elsif (my($gd) = m/^\s+Grown Defects:\s+(\d+)/) {
$p{defects} = int($gd);
} elsif (/^$/) {
if ($p{capacity} == 0 and $p{name} =~ /SCA HSBP/) {
# HSBP is not a disk, so do not consider this an error
# http://support.gateway.com/s/Servers/COMPO/MOTHERBD/4000832/4000832si69.shtml
# Raid Hot Swap Backplane driver (recognized as "ESG-SHV SCA HSBP M16 SCSI Processor Device")
# Chn/ID/LUN: B/06/0 Name: ESG-SHV SCA HSBP M16 0.05
# Capacity [MB]: 0 To Log. Drive: --
next;
}
$pd{$p{id}} = { %p };
} else {
warn "[$section] [$_]";
$this->unknown;
}
} elsif ($section eq 'Logical Drives') {
# Number: 3 Status: ok
# Slave Number: 15 Status: ok (older kernels)
# Capacity [MB]: 69974 Type: Disk
if (my($num, $s) = m/^\s+(?:Slave )?Number:\s+(\d+)\s+Status:\s+(\S+)/) {
$l{number} = int($num);
$l{status} = $s;
} elsif (my($unit, $c, $t) = m/^\s+Capacity\s\[(.B)\]:\s+(\d+)\s+Type:\s+(\S+)/) {
$l{capacity} = "$c $unit";
$l{type} = $t;
} elsif (my($md, $id) = m/^\s+Missing Drv\.:\s+(\d+)\s+Invalid Drv\.:\s+(\d+|--)/) {
$l{missing} = int($md);
$l{invalid} = int($id);
} elsif (my($n) = m/^\s+To Array Drv\.:\s+(\d+|--)/) {
$l{array} = $n;
} elsif (/^$/) {
$ld{$l{number}} = { %l };
} else {
warn "[$section] [$_]";
$this->unknown;
}
} elsif ($section eq 'Array Drives') {
# Number: 0 Status: fail
# Capacity [MB]: 349872 Type: RAID-5
if (my($num, $s) = m/^\s+Number:\s+(\d+)\s+Status:\s+(\S+)/) {
$a{number} = int($num);
$a{status} = $s;
} elsif (my($unit, $c, $t) = m/^\s+Capacity\s\[(.B)\]:\s+(\d+)\s+Type:\s+(\S+)/) {
$a{capacity} = "$c $unit";
$a{type} = $t;
} elsif (/^(?: --)?$/) {
if (%a) {
$ad{$a{number}} = { %a };
}
} else {
warn "[$section] [$_]";
$this->unknown;
}
} elsif ($section eq 'Host Drives') {
# nothing useful
} elsif ($section eq 'Controller Events') {
# nothing useful
}
}
close($fh);
$c{$c} = { id => $c, array => { %ad }, logical => { %ld }, physical => { %pd } };
}
return \%c;
}
sub check {
my $this = shift;
# status messages pushed here
my @status;
my $controllers = $this->parse;
# process each controller separately
for my $c (values %$controllers) {
# array status
my @ad;
for my $n (sort {$a cmp $b} keys %{$c->{array}}) {
my $ad = $c->{array}->{$n};
if ($ad->{status} ne "ready") {
$this->critical;
}
push(@ad, "Array $ad->{number}($ad->{type}) $ad->{status}");
}
# older raids have no Array drives, Look into Logical Drives for type!=Disk
unless (@ad) {
for my $n (sort {$a cmp $b} keys %{$c->{logical}}) {
my $ld = $c->{logical}->{$n};
if ($ld->{type} eq "Disk") {
next;
}
# emulate Array Drive
my $s = "Array($ld->{type}) $ld->{status}";
# check for missing drives
if ($ld->{missing} > 0) {
$this->warning;
$s .= " ($ld->{missing} missing drives)";
}
push(@ad, $s);
}
}
# logical drive status
my %ld;
for my $n (sort {$a cmp $b} keys %{$c->{logical}}) {
my $ld = $c->{logical}->{$n};
if ($ld->{status} ne "ok") {
$this->critical;
}
push(@{$ld{$ld->{status}}}, $ld->{number});
}
# physical drive status
my @pd;
for my $n (sort {$a cmp $b} keys %{$c->{physical}}) {
my $pd = $c->{physical}->{$n};
my @ds;
# TODO: make tresholds configurable
if ($pd->{defects} > 300) {
$this->critical;
push(@ds, "grown defects critical: $pd->{defects}");
} elsif ($pd->{defects} > 30) {
$this->warning;
push(@ds, "grown defects warning: $pd->{defects}");
}
# report disk being not assigned
if ($pd->{drive} eq '--') {
push(@ds, "not assigned");
}
if (@ds) {
push(@pd, "Disk $pd->{id}($pd->{name}) ". join(', ', @ds));
}
}
my @cd;
push(@cd, @ad) if @ad;
push(@cd, "Logical Drives: ". $this->join_status(\%ld));
push(@cd, @pd) if @pd;
push(@status, "Controller $c->{id}: ". join('; ', @cd));
}
return unless @status;
# denote this plugin as ran ok
$this->ok;
$this->message(join('; ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_GDTH
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/hp_msa.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HP_MSA';
package App::Monitoring::Plugin::CheckRaid::Plugins::hp_msa;
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use App::Monitoring::Plugin::CheckRaid::SerialLine;
use strict;
use warnings;
sub active {
my $this = shift;
return $this->detect;
}
# check from /sys if there are any MSA VOLUME's present.
sub detect {
my $this = shift;
# allow --plugin-option=hp_msa-enabled to force this plugin to be enabled
return 1 if exists $this->{options}{'hp_msa-enabled'};
for my $file (</sys/block/*/device/model>) {
open my $fh, '<', $file or next;
my $model = <$fh>;
close($fh);
return 1 if ($model =~ /^MSA.+VOLUME/);
}
return 0;
}
sub check {
my $this = shift;
# allow --plugin-option=hp_msa-serial=/dev/ttyS2 to specify serial line
my $ctldevice = $this->{options}{'hp_msa-serial'} || '/dev/ttyS0';
# status messages pushed here
my @status;
my %opts = ();
$opts{lockdir} = $this->{lockdir} if $this->{lockdir};
my $modem = App::Monitoring::Plugin::CheckRaid::SerialLine->new($ctldevice, %opts);
my $fh = $modem->open();
unless ($fh) {
$this->warning;
$this->message("Can't open $ctldevice");
return;
}
# check first controller
print $fh "\r";
print $fh "show globals\r";
print $fh "show this_controller\r";
print $fh "show other_controller\r";
# this will issue termination match, ie. invalid command
print $fh "exit\r";
my ($c, %c, %t);
while (<$fh>) {
chomp;
s/[\n\r]$//;
last if /Invalid CLI command/;
# Temperature:
# EMU: 23 Celsius, 73 Fahrenheit
# PS1: 22 Celsius, 71 Fahrenheit
# PS2: 22 Celsius, 71 Fahrenheit
if (my($s, $c) = /(\S+): (\d+) Celsius,\s+\d+ Fahrenheit/) {
$t{$s} = int($c);
next;
}
# Controller 1 (right controller):
if (my($s) = /^(Controller \d+)/) {
$c = $s;
$c{$c} = [];
next;
}
# Surface Scan: Running, LUN 10 (68% Complete)
if (my($s, $m) = /Surface Scan:\s+(\S+)[,.]\s*(.*)/) {
if ($s eq 'Running') {
my ($l, $p) = $m =~ m{(LUN \d+) \((\d+)% Complete\)};
push(@{$c{$c}}, "Surface: $l ($p%)");
$this->warning;
} elsif ($s ne 'Complete') {
push(@{$c{$c}}, "Surface: $s, $m");
$this->warning;
}
next;
}
# Rebuild Status: Running, LUN 0 (67% Complete)
if (my($s, $m) = /Rebuild Status:\s+(\S+)[,.]\s*(.*)/) {
if ($s eq 'Running') {
my ($l, $p) = $m =~ m{(LUN \d+) \((\d+)% Complete\)};
push(@{$c{$c}}, "Rebuild: $l ($p%)");
$this->warning;
} elsif ($s ne 'Complete') {
push(@{$c{$c}}, "Rebuild: $s, $m");
$this->warning;
}
next;
}
# Expansion: Complete.
if (my($s, $m) = /Expansion:\s+(\S+)[.,]\s*(.*)/) {
if ($s eq 'Running') {
my ($l, $p) = $m =~ m{(LUN \d+) \((\d+)% Complete\)};
push(@{$c{$c}}, "Expansion: $l ($p%)");
$this->warning;
} elsif ($s ne 'Complete') {
push(@{$c{$c}}, "Expansion: $s, $m");
$this->warning;
}
next;
}
}
$modem->close();
foreach $c (sort { $a cmp $b } keys %c) {
my $s = $c{$c};
$s = join(', ', @$s);
$s = 'OK' unless $s;
push(@status, "$c: $s");
}
# check that no temp is over the treshold
my $warn = 28;
my $crit = 33;
while (my($t, $c) = each %t) {
if ($c > $crit) {
push(@status, "$t: ${c}C");
$this->critical;
} elsif ($c > $warn) {
push(@status, "$t: ${c}C");
$this->warning;
}
}
return unless @status;
$this->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HP_MSA
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/hpacucli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPACUCLI';
package App::Monitoring::Plugin::CheckRaid::Plugins::hpacucli;
## hpacucli/hpssacli support
#
# driver developers recommend to use cciss_vol_status for monitoring,
# hpacucli/hpssacli shouldn't be used for monitoring due they obtaining global
# kernel lock while cciss_vol_status does not. cciss_vol_status is designed for
# monitoring
# https://github.com/glensc/nagios-plugin-check_raid/issues/114#issuecomment-138866801
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
use constant E_NO_LOGICAL_DEVS => 'The specified device does not have any logical drives';
sub program_names {
shift->{name};
}
sub commands {
{
'controller status' => ['-|', '@CMD', 'controller', 'all', 'show', 'status'],
'logicaldrive status' => ['-|', '@CMD', 'controller', '$target', 'logicaldrive', 'all', 'show'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
(
"CHECK_RAID ALL=(root) NOPASSWD: $cmd controller all show status",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd controller * logicaldrive all show",
);
}
# if --plugin-option=hpacucli-target=slot=0 is specified
# filter only allowed values
sub filter_targets {
my ($this, $targets) = @_;
my $cli_opts = $this->{options}{'hpacucli-target'};
if (!$cli_opts) {
return $targets;
}
my %res;
my @filters = split(/,/, $cli_opts);
for my $filter (@filters) {
if (exists $targets->{$filter}) {
$res{$filter} = $targets->{$filter};
} else {
$this->critical->message("Controller $filter not found");
}
}
return \%res;
}
# split:
# '(Embedded) (RAID Mode)'
# to:
# [ 'Embedded', 'RAID Mode' ]
sub split_controller_modes {
my ($modes) = @_;
my @parts;
push @parts, $1 while $modes =~ /\((.*?)\)/g;
return \@parts;
}
sub scan_targets {
my $this = shift;
# TODO: allow target customize:
# hpacucli <target> is of format:
# [controller all|slot=#|wwn=#|chassisname="AAA"|serialnumber=#|chassisserialnumber=#|ctrlpath=#:# ]
# [array all|<id>]
# [physicaldrive all|allunassigned|[#:]#:#|[#:]#:#-[#:]#:#]
# [logicaldrive all|#]
# [enclosure all|#:#|serialnumber=#|chassisname=#]
# [licensekey all|<key>]
# Scan controllers
my (%targets, $target);
my $fh = $this->cmd('controller status');
while (<$fh>) {
chomp;
# skip empty lines and artificial comments (added by this project)
next if /^$/ or /^#/;
# skip known noise
if (
/FIRMWARE UPGRADE REQUIRED: /
|| /^\s{27}/
) {
next;
}
# Numeric slot
if (my($controller, $slot, $modes) = /
^(\S.+)\sin\sSlot
\s(\S+?) # slot number
(?: # optional mode
\s(\(.+\))
)?$
/x) {
$target = "slot=$slot";
$targets{$target} = {
target => $target,
controller => $controller,
slot => $slot,
modes => split_controller_modes($modes || ''),
};
$this->unknown if $slot !~ /^\d+/;
next;
}
# Named Entry
if (my($controller, $cn) = /^(\S.+) in (.+)/) {
$target = "chassisname=$cn";
$targets{$target} = {
target => $target,
controller => $controller,
chassisname => $cn,
};
next;
}
# Other statuses, try "key: value" pairs
if (my ($key, $value) = /^\s*(.+?):\s+(.+?)$/) {
$targets{$target}{$key} = $value;
next;
}
warn "Unparsed: [$_]\n";
}
close $fh;
return $this->filter_targets(\%targets);
}
# Scan logical drives
sub scan_luns {
my ($this, $targets) = @_;
my @luns;
# sort by target to ensure consistent results
for my $target (sort {$a->{target} cmp $b->{target}} values(%$targets)) {
# check each controller
my $fh = $this->cmd('logicaldrive status', { '$target' => $target->{target} });
my $index = -1;
my @array;
my %array;
while (<$fh>) {
chomp;
# skip empty lines and artificial comments (added by this project)
next if /^$/ or /^#/;
# Error: The controller identified by "slot=attr_value_slot_unknown" was not detected.
if (/^Error:\s/) {
# store it somewhere. should it be appended?
($target->{'error'}) = /^Error:\s+(.+?)\.?\s*$/;
$this->unknown;
next;
}
# "array A"
# "array A (Failed)"
# "array B (Failed)"
if (my($a, $s) = /^\s+array (\S+)(?:\s*\((\S+)\))?$/) {
$index++;
# Offset 0 is Array own status
# XXX: I don't like this one: undef could be false positive
$target->{'array'}[$index]{status} = $s || 'OK';
$target->{'array'}[$index]{name} = $a;
next;
}
# logicaldrive 1 (68.3 GB, RAID 1, OK)
# capture only status
if (my($drive, $size, $raid, $status) = /^\s+logicaldrive (\d+) \(([\d.]+ .B), ([^,]+), ([^\)]+)\)$/) {
warn "Index out of bounds" if $index < 0; # XXX should not happen
# Offset 1 is each logical drive status
my $ld = {
'id' => $drive,
'status' => $status,
'size' => $size,
'raid' => $raid,
};
push(@{$target->{'array'}[$index]{logicaldrives}}, $ld);
next;
}
# skip known noise
if (
/\s+Type "help" for more details/
# Controller name: exact match
|| /^\Q$target->{controller}\E\s/
# loose match, some test data seems malformed
|| / in Slot \d/
|| /^FIRMWARE UPGRADE REQUIRED:/
|| /^\s{27}/
) {
next;
}
warn "Unhandled: [$_]\n";
}
$this->unknown unless close $fh;
push(@luns, $target);
}
return \@luns;
}
# parse hpacucli output into logical structure
sub parse {
my $this = shift;
my $targets = $this->scan_targets;
if (!$targets) {
return $targets;
}
return $this->scan_luns($targets);
}
# format lun (logicaldevice) status
# update check status if problems found
sub lstatus {
my ($this, $ld) = @_;
my $s = $ld->{status};
if ($s eq 'OK' or $s eq 'Disabled') {
} elsif ($s eq 'Failed' or $s eq 'Interim Recovery Mode') {
$this->critical;
} elsif ($s eq 'Rebuild' or $s eq 'Recover') {
$this->warning;
}
return "LUN$ld->{id}:$s";
}
# format array status
# update check status if problems found
sub astatus {
my ($this, $array) = @_;
if ($array->{status} ne 'OK') {
$this->critical;
}
return "Array $array->{name}($array->{status})";
}
# format controller status
# updates check status if problems found
sub cstatus {
my ($this, $c) = @_;
my (@s, $s);
# always include controller status
push(@s, $c->{'Controller Status'} || 'ERROR');
if ($c->{'Controller Status'} ne 'OK') {
$this->critical;
}
if ($c->{error}) {
if ($c->{error} eq E_NO_LOGICAL_DEVS) {
$this->noraid;
push(@s, 'Not configured');
} else {
$this->unknown;
push(@s, $c->{error});
}
}
# print those only if not ok and configured
if (($s = $c->{'Cache Status'}) && $s !~ /^(OK|Not Configured)/) {
push(@s, "Cache: $s");
$this->critical;
}
if (($s = $c->{'Battery/Capacitor Status'}) && $s !~ /^(OK|Not Configured)/) {
push(@s, "Battery: $s");
$this->critical;
}
# start with identifyier
my $name = $c->{chassisname} || $c->{controller};
return $name . '[' . join(', ', @s) . ']';
}
sub check {
my $this = shift;
my $ctrls = $this->parse;
unless ($ctrls) {
$this->warning->message("No Controllers were found on this machine");
return;
}
my @status;
foreach my $ctrl (@$ctrls) {
my @astatus;
foreach my $array (@{$ctrl->{array}}) {
my @lstatus;
foreach my $ld (@{$array->{logicaldrives}}) {
push(@lstatus, $this->lstatus($ld));
}
push(@astatus, $this->astatus($array). '['. join(',', @lstatus). ']');
}
my $cstatus = $this->cstatus($ctrl);
$cstatus .= ': '. join(', ', @astatus) if @astatus;
push(@status, $cstatus);
}
return unless @status;
$this->ok->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPACUCLI
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/hpssacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPSSACLI';
package App::Monitoring::Plugin::CheckRaid::Plugins::hpssacli;
# This plugin extends hpacucli plugin,
# with the only difference that different program name will be used.
use base 'App::Monitoring::Plugin::CheckRaid::Plugins::hpacucli';
use strict;
use warnings;
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_HPSSACLI
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/ips.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_IPS';
package App::Monitoring::Plugin::CheckRaid::Plugins::ips;
# Serveraid IPS
# Tested on IBM xSeries 346 servers with Adaptec ServeRAID 7k controllers.
# The ipssend version was v7.12.14.
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
qw(ipssend);
}
sub commands {
{
'list logical drive' => ['-|', '@CMD', 'GETCONFIG', '1', 'LD'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
"CHECK_RAID ALL=(root) NOPASSWD: $cmd getconfig 1 LD"
}
sub check {
my $this = shift;
# status messages pushed here
my @status;
my $n;
my $fh = $this->cmd('list logical drive');
while (<$fh>) {
if (/drive number (\d+)/i){
$n = $1;
next;
}
next unless $n;
next unless $this->valid($n);
next unless (my($s, $c) = /Status .*: (\S+)\s+(\S+)/);
if ($c =~ /SYN|RBL/i) { # resynching
$this->resync;
} elsif ($c !~ /OKY/i) { # not OK
$this->critical;
}
push(@status, "$n:$s");
}
close $fh;
return unless @status;
$this->ok->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_IPS
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/lsraid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSRAID';
package App::Monitoring::Plugin::CheckRaid::Plugins::lsraid;
# Linux, software RAID
# Broken: missing test data
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
shift->{name};
}
sub commands {
{
'list' => ['-|', '@CMD', '-A', '-p'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -A -p"
}
sub check {
my $this = shift;
# status messages pushed here
my @status;
my $fh = $this->cmd('list');
while (<$fh>) {
next unless (my($n, $s) = m{/dev/(\S+) \S+ (\S+)});
next unless $this->valid($n);
if ($s =~ /good|online/) {
# no worries
} elsif ($s =~ /sync/) {
$this->warning;
} else {
$this->critical;
}
push(@status, "$n:$s");
}
close $fh;
return unless @status;
$this->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSRAID
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/lsscsi.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSSCSI';
package App::Monitoring::Plugin::CheckRaid::Plugins::lsscsi;
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
shift->{name};
}
sub commands {
{
'lsscsi list' => ['-|', '@CMD', '-g'],
}
}
# lists contoller devices (type=storage)
# this will fail (return empty list) if sg module is not present
# return /dev/sgX nodes
sub list_sg {
my $this = shift;
my @scan = $this->scan;
my @devs = map { $_->{sgnode} } grep { $_->{type} eq 'storage' && $_->{sgnode} ne '-' } @scan;
return wantarray ? @devs : \@devs;
}
# list disk nodes one for each controller
# return /dev/sdX nodes
sub list_dd {
my $this = shift;
my @scan = $this->scan;
my @devs = map { $_->{devnode} } grep { $_->{type} eq 'disk' && $_->{devnode} ne '-' && $_->{sgnode} } @scan;
return wantarray ? @devs : \@devs;
}
# scan lsscsi output
sub scan {
my $this = shift;
# cache inside single run
return wantarray ? @{$this->{sdevs}} : $this->{sdevs} if $this->{sdevs};
# Scan such output:
# [0:0:0:0] disk HP LOGICAL VOLUME 3.00 /dev/sda /dev/sg0
# [0:3:0:0] storage HP P410i 3.00 - /dev/sg1
# or without sg driver:
# [0:0:0:0] disk HP LOGICAL VOLUME 3.00 /dev/sda -
# [0:3:0:0] storage HP P410i 3.00 - -
my $fh = $this->cmd('lsscsi list');
my @sdevs;
while (<$fh>) {
chop;
if (my($hctl, $type, $vendor, $model, $rev, $devnode, $sgnode) = m{^
\[([\d:]+)\] # SCSI Controller, SCSI bus, SCSI target, and SCSI LUN
\s+(\S+) # type
\s+(\S+) # vendor
\s+(.*?) # model, match everything as it may contain spaces
\s+(\S+) # revision
\s+((?:/dev/\S+|-)) # /dev node
\s+((?:/dev/\S+|-)) # /dev/sg node
}x) {
push(@sdevs, {
'hctl' => $hctl,
'type' => $type,
'vendor' => $vendor,
'model' => $model,
'rev' => $rev,
'devnode' => $devnode,
'sgnode' => $sgnode,
});
}
}
close $fh;
$this->{sdevs} = \@sdevs;
return wantarray ? @sdevs : \@sdevs;
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSSCSI
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/lsvg.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSVG';
package App::Monitoring::Plugin::CheckRaid::Plugins::lsvg;
# AIX LVM
# Status: broken (no test data)
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
shift->{name};
}
sub commands {
{
'lsvg' => ['-|', '@CMD'],
'lsvg list' => ['-|', '@CMD', '-l', '$vg'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
(
"CHECK_RAID ALL=(root) NOPASSWD: $cmd",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -l *",
)
}
sub check {
my $this = shift;
# status messages pushed here
my @status;
my @vg;
my $fh = $this->cmd('lsvg');
while (<$fh>) {
chomp;
push @vg, $_;
}
close $fh;
foreach my $vg (@vg) {
next unless $this->valid($vg); # skip entire VG
my $fh = $this->cmd('lsvg list', { '$vg' => $vg });
while (<$fh>) {
my @f = split /\s/;
my ($n, $s) = ($f[0], $f[5]);
next if (!$this->valid($n) or !$s);
next if ($f[3] eq $f[2]); # not a mirrored LV
if ($s =~ m#open/(\S+)#i) {
$s = $1;
if ($s ne 'syncd') {
$this->critical;
}
push(@status, "lvm:$n:$s");
}
}
close $fh;
}
return unless @status;
$this->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_LSVG
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/mdstat.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MDSTAT';
package App::Monitoring::Plugin::CheckRaid::Plugins::mdstat;
# Linux Multi-Device (md)
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub commands {
{
'mdstat' => ['<', '/proc/mdstat'],
}
}
sub active {
my ($this) = @_;
# easy way out. no /proc/mdstat
return 0 unless -e $this->{commands}{mdstat}[1];
# extra check if mdstat is empty
my @md = $this->parse;
return $#md >= 0;
}
sub parse {
my $this = shift;
my (@md, %md);
my $fh = $this->cmd('mdstat');
my $arr_checking = 0;
while (<$fh>) {
chomp;
# skip first line
next if (/^Personalities : /);
# kernel-3.0.101/drivers/md/md.c, md_seq_show
# md1 : active raid1 sdb2[0] sda2[1]
if (my($dev, $active, $ro, $rest) = m{^
(\S+)\s+:\s+ # mdname
(\S+)\s+ # active: "inactive", "active"
(\((?:auto-)?read-only\)\s+)? # readonly
(.+) # personality name + disks
}x) {
my @parts = split /\s/, $rest;
my $re = qr{^
(\S+) # devname
(?:\[(\d+)\]) # desc_nr
(?:\((.)\))? # flags: (W|F|S) - WriteMostly, Faulty, Spare
$}x;
my @disks = ();
my $personality;
while (my($disk) = pop @parts) {
last if !$disk;
if ($disk !~ $re) {
$personality = $disk;
last;
}
my($dev, $number, $flags) = $disk =~ $re;
push(@disks, {
'dev' => $dev,
'number' => int($number),
'flags' => $flags || '',
});
}
die "Unexpected parse" if @parts;
# first line resets %md
%md = (dev => $dev, personality => $personality, readonly => $ro, active => $active, disks => [ @disks ]);
next;
}
# variations:
#" 8008320 blocks [2/2] [UU]"
#" 58291648 blocks 64k rounding" - linear
#" 5288 blocks super external:imsm"
#" 20969472 blocks super 1.2 512k chunks"
#
# Metadata version:
# This is one of
# - 'none' for arrays with no metadata (good luck...)
# - 'external' for arrays with externally managed metadata,
# - or N.M for internally known formats
#
if (my($b, $mdv, $status) = m{^
\s+(\d+)\sblocks\s+ # blocks
# metadata version
(super\s(?:
(?:\d+\.\d+) | # N.M
(?:external:\S+) |
(?:non-persistent)
))?\s*
(.+) # mddev->pers->status (raid specific)
$}x) {
# linux-2.6.33/drivers/md/dm-raid1.c, device_status_char
# A => Alive - No failures
# D => Dead - A write failure occurred leaving mirror out-of-sync
# S => Sync - A sychronization failure occurred, mirror out-of-sync
# R => Read - A read failure occurred, mirror data unaffected
# U => for the rest
my ($s) = $status =~ /\s+\[([ADSRU_]+)\]/;
$md{status} = $s || '';
$md{blocks} = int($b);
$md{md_version} = $mdv;
# if external try to parse dev
if ($mdv) {
($md{md_external}) = $mdv =~ m{external:(\S+)};
}
next;
}
# linux-2.6.33/drivers/md/md.c, md_seq_show
if (my($action) = m{(resync=(?:PENDING|DELAYED))}) {
$md{resync_status} = $action;
next;
}
# linux-2.6.33/drivers/md/md.c, status_resync
# [==>..................] resync = 13.0% (95900032/732515712) finish=175.4min speed=60459K/sec
# [=>...................] check = 8.8% (34390144/390443648) finish=194.2min speed=30550K/sec
if (my($action, $perc, $eta, $speed) = m{(resync|recovery|reshape)\s+=\s+([\d.]+%) \(\d+/\d+\) finish=([\d.]+min) speed=(\d+K/sec)}) {
$md{resync_status} = "$action:$perc $speed ETA: $eta";
next;
} elsif (($perc, $eta, $speed) = m{check\s+=\s+([\d.]+%) \(\d+/\d+\) finish=([\d.]+min) speed=(\d+K/sec)}) {
$md{check_status} = "check:$perc $speed ETA: $eta";
$arr_checking = 1;
next;
}
# we need empty line denoting end of one md
next unless /^\s*$/;
next unless $this->valid($md{dev});
push(@md, { %md } ) if %md;
}
close $fh;
# One of the arrays is in checking state, which could be because there is a scheduled sync of all MD arrays
# In such a case, all of the arrays are scheduled to by checked, but only one of them is actually running the check
# while the others are in "resync=DELAYED" state.
# We don't want to receive notifications in such case, so we check for this particular case here
if ($arr_checking && scalar(@md) >= 2) {
foreach my $dev (@md) {
if ($dev->{resync_status} && $dev->{resync_status} eq "resync=DELAYED") {
delete $dev->{resync_status};
$dev->{check_status} = "check=DELAYED";
}
}
}
return wantarray ? @md : \@md;
}
sub check {
my $this = shift;
my (@status);
my @md = $this->parse;
foreach (@md) {
my %md = %$_;
# common status
my $size = $this->format_bytes($md{blocks} * 1024);
my $personality = $md{personality} ? " $md{personality}" : "";
my $s = "$md{dev}($size$personality):";
# failed disks
my @fd = map { $_->{dev} } grep { $_->{flags} =~ /F/ } @{$md{disks}};
# raid0 is just there or its not. raid0 can't degrade.
# same for linear, no $md_status available
if ($personality =~ /linear|raid0/) {
$s .= "OK";
} elsif ($md{resync_status}) {
$this->resync;
$s .= "$md{status} ($md{resync_status})";
} elsif ($md{check_status}) {
$this->check_status;
$s .= "$md{status} ($md{check_status})";
} elsif ($md{status} =~ /_/) {
$this->critical;
my $fd = join(',', @fd);
$s .= "F:$fd:$md{status}";
} elsif (@fd > 0) {
# FIXME: this is same as above?
$this->warning;
$s .= "hot-spare failure:". join(",", @fd) .":$md{status}";
} else {
$s .= "$md{status}";
}
push(@status, $s);
}
return unless @status;
# denote this plugin as ran ok
$this->ok;
$this->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MDSTAT
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGACLI';
package App::Monitoring::Plugin::CheckRaid::Plugins::megacli;
# MegaRAID SAS 8xxx controllers
# based on info from here:
# http://www.bxtra.net/Articles/2008-09-16/Dell-Perc6i-RAID-Monitoring-Script-using-MegaCli-LSI-CentOS-52-64-bits
# TODO: http://www.techno-obscura.com/~delgado/code/check_megaraid_sas
# TODO: process several adapters
# TODO: process drive temperatures
# TODO: check error counts
# TODO: hostspare information
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
qw(MegaCli64 MegaCli megacli);
}
sub commands {
{
'pdlist' => ['-|', '@CMD', '-PDList', '-aALL', '-NoLog'],
'ldinfo' => ['-|', '@CMD', '-LdInfo', '-Lall', '-aALL', '-NoLog'],
'battery' => ['-|', '@CMD', '-AdpBbuCmd', '-GetBbuStatus', '-aALL', '-NoLog'],
}
}
# TODO: process from COMMANDS
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
(
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -PDList -aALL -NoLog",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -LdInfo -Lall -aALL -NoLog",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -AdpBbuCmd -GetBbuStatus -aALL -NoLog",
);
}
# parse physical devices
sub parse_pd {
my $this = shift;
my (@pd, %pd);
my $rc = -1;
my $fh = $this->cmd('pdlist');
while (<$fh>) {
if (my($s) = /Device Id: (\S+)/) {
push(@pd, { %pd }) if %pd;
%pd = ( dev => $s, state => undef, name => undef, predictive => undef );
next;
}
if (my($s) = /Firmware state: (.+)/) {
# strip the extra state:
# 'Hotspare, Spun Up'
# 'Hotspare, Spun down'
# 'Online, Spun Up'
# 'Online, Spun Up'
# 'Online, Spun down'
# 'Unconfigured(bad)'
# 'Unconfigured(good), Spun Up'
# 'Unconfigured(good), Spun down'
$s =~ s/,.+//;
$pd{state} = $s;
if (defined($pd{predictive})) {
$pd{state} = $pd{predictive};
}
next;
}
if (my($s) = /Predictive Failure Count: (\d+)/) {
if ($s > 0) {
$pd{predictive} = 'Predictive';
}
next;
}
if (my($s) = /Inquiry Data: (.+)/) {
# trim some spaces
$s =~ s/\s+/ /g; $s =~ s/^\s+|\s+$//g;
$pd{name} = $s;
next;
}
if (my($s) = /Exit Code: (\d+x\d+)/) {
$rc = hex($s);
}
else {
$rc = 0;
}
}
push(@pd, { %pd }) if %pd;
$this->critical unless close $fh;
$this->critical if $rc;
return \@pd;
}
sub parse_ld {
my $this = shift;
my (@ld, %ld);
my $rc = -1;
my $fh = $this->cmd('ldinfo');
while (<$fh>) {
if (my($drive_id, $target_id) = /Virtual (?:Disk|Drive)\s*:\s*(\d+)\s*\(Target Id:\s*(\d+)\)/i) {
push(@ld, { %ld }) if %ld;
# Default to DriveID:TargetID in case no Name is given ...
%ld = ( name => "DISK$drive_id.$target_id", state => undef );
next;
}
if (my($name) = /Name\s*:\s*(\S+)/) {
# Add a symbolic name, if given
$ld{name} = $name;
next;
}
if (my($s) = /Virtual Drive Type\s*:\s*(\S+)/) {
$ld{type} = $s;
next;
}
if (my($s) = /State\s*:\s*(\S+)/) {
$ld{state} = $s;
next;
}
if (my($s) = /Default Cache Policy\s*:\s*(.+)/) {
$ld{default_cache} = [split /,\s*/, $s];
next;
}
if (my($s) = /Current Cache Policy\s*:\s*(.+)/) {
$ld{current_cache} = [split /,\s*/, $s];
next;
}
if (my($s) = /Exit Code: (\d+x\d+)/) {
$rc = hex($s);
} else {
$rc = 0;
}
}
push(@ld, { %ld }) if %ld;
$this->critical unless close $fh;
$this->critical if $rc;
return \@ld;
}
# check battery
sub parse_bbu {
my $this = shift;
return undef unless $this->bbu_monitoring;
my %default_bbu = (
name => undef, state => '???', charging_status => '???', missing => undef,
learn_requested => undef, replacement_required => undef,
learn_cycle_requested => undef, learn_cycle_active => '???',
pack_will_fail => undef, temperature => undef, temperature_state => undef,
voltage => undef, voltage_state => undef
);
my (@bbu, %bbu);
my $fh = $this->cmd('battery');
while (<$fh>) {
# handle when bbu status get gives an error. see issue #32
if (my($s) = /Get BBU Status Failed/) {
last;
}
if (my($s) = /BBU status for Adapter: (.+)/) {
push(@bbu, { %bbu }) if %bbu;
%bbu = %default_bbu;
$bbu{name} = $s;
next;
}
#=cut
# according to current sample data, Battery State never has value
if (my($s) = /Battery State\s*: ?(.*)/i) {
if (!$s) { $s = 'Faulty'; };
$bbu{state} = $s;
next;
}
#=cut
if (my($s) = /Charging Status\s*: (\w*)/) {
$bbu{charging_status} = $s;
next;
}
if (my($s) = /Battery Pack Missing\s*: (\w*)/) {
$bbu{missing} = $s;
next;
}
if (my($s) = /Battery Replacement required\s*: (\w*)/) {
$bbu{replacement_required} = $s;
next;
}
if (my($s) = /Learn Cycle Requested\s*: (\w*)/) {
$bbu{learn_cycle_requested} = $s;
next;
}
if (my($s) = /Learn Cycle Active\s*: (\w*)/) {
$bbu{learn_cycle_active} = $s;
next;
}
if (my($s) = /Pack is about to fail & should be replaced\s*: (\w*)/) {
$bbu{pack_will_fail} = $s;
next;
}
# Temperature: 18 C
if (my($s) = /Temperature: (\d+) C/) {
$bbu{temperature} = $s;
next;
}
# Temperature : OK
if (my($s) = / Temperature\s*: (\w*)/) {
$bbu{temperature_state} = $s;
next;
}
# Voltage: 4074 mV
if (my($s) = /Voltage: (\d+) mV/) {
$bbu{voltage} = $s;
next;
}
# Voltage : OK
if (my($s) = /Voltage\s*: (\w*)/) {
$bbu{voltage_state} = $s;
next;
}
}
$this->critical unless close $fh;
push(@bbu, { %bbu }) if %bbu;
return \@bbu;
}
sub parse {
my $this = shift;
my $pd = $this->parse_pd;
my $ld = $this->parse_ld;
my $bbu = $this->parse_bbu;
my @devs = @$pd if $pd;
my @vols = @$ld if $ld;
my @bats = @$bbu if $bbu;
return {
logical => $ld,
physical => $pd,
battery => $bbu,
};
}
sub check {
my $this = shift;
my $c = $this->parse;
my @vstatus;
foreach my $vol (@{$c->{logical}}) {
# skip CacheCade for now. #91
if ($vol->{type} && $vol->{type} eq 'CacheCade') {
next;
}
push(@vstatus, sprintf "%s:%s", $vol->{name}, $vol->{state});
if ($vol->{state} ne 'Optimal') {
$this->critical;
}
# check cache policy, #65
my @wt = grep { /WriteThrough/ } @{$vol->{current_cache}};
if (@wt) {
my @default = grep { /WriteThrough/ } @{$vol->{default_cache}};
# alert if WriteThrough is configured in default
$this->cache_fail unless @default;
push(@vstatus, "WriteCache:DISABLED");
}
}
my %dstatus;
foreach my $dev (@{$c->{physical}}) {
if ($dev->{state} eq 'Online' || $dev->{state} eq 'Hotspare' || $dev->{state} eq 'Unconfigured(good)' || $dev->{state} eq 'JBOD') {
push(@{$dstatus{$dev->{state}}}, sprintf "%02d", $dev->{dev});
} elsif ($dev->{state} eq 'Predictive') {
$this->warning;
push(@{$dstatus{$dev->{state}}}, sprintf "%02d (%s)", $dev->{dev}, $dev->{name});
} else {
$this->critical;
# TODO: process other statuses
push(@{$dstatus{$dev->{state}}}, sprintf "%02d (%s)", $dev->{dev}, $dev->{name});
}
}
my (%bstatus, @bpdata, @blongout);
foreach my $bat (@{$c->{battery}}) {
if ($bat->{state} !~ /^(Operational|Optimal)$/) {
# BBU learn cycle in progress.
if ($bat->{charging_status} =~ /^(Charging|Discharging)$/ && $bat->{learn_cycle_active} eq 'Yes') {
$this->bbulearn;
} else {
$this->critical;
}
}
if ($bat->{missing} ne 'No') {
$this->critical;
}
if ($bat->{replacement_required} ne 'No') {
$this->critical;
}
if (defined($bat->{pack_will_fail}) && $bat->{pack_will_fail} ne 'No') {
$this->critical;
}
if ($bat->{temperature_state} ne 'OK') {
$this->critical;
}
if ($bat->{voltage_state} ne 'OK') {
$this->critical;
}
# Short output.
#
# CRITICAL: megacli:[Volumes(1): NoName:Optimal; Devices(2): 06,07=Online; Batteries(1): 0=Non Operational]
push(@{$bstatus{$bat->{state}}}, sprintf "%d", $bat->{name});
# Performance data.
# Return current battery temparature & voltage.
#
# Battery0=18;4074
push(@bpdata, sprintf "Battery%s_T=%s;;;; Battery%s_V=%s;;;;", $bat->{name}, $bat->{temperature}, $bat->{name}, $bat->{voltage});
# Long output.
# Detailed plugin output.
#
# Battery0:
# - State: Non Operational
# - Missing: No
# - Replacement required: Yes
# - About to fail: No
# - Temperature: OK (18 °C)
# - Voltage: OK (4015 mV)
push(@blongout, join("\n", grep {/./}
"Battery$bat->{name}:",
" - State: $bat->{state}",
" - Charging status: $bat->{charging_status}",
" - Learn cycle requested: $bat->{learn_cycle_requested}",
" - Learn cycle active: $bat->{learn_cycle_active}",
" - Missing: $bat->{missing}",
" - Replacement required: $bat->{replacement_required}",
defined($bat->{pack_will_fail}) ? " - About to fail: $bat->{pack_will_fail}" : "",
" - Temperature: $bat->{temperature_state} ($bat->{temperature} C)",
" - Voltage: $bat->{voltage_state} ($bat->{voltage} mV)",
));
}
my @cstatus;
push(@cstatus, 'Volumes(' . ($#{$c->{logical}} + 1) . '): ' . join(',', @vstatus));
push(@cstatus, 'Devices(' . ($#{$c->{physical}} + 1) . '): ' . $this->join_status(\%dstatus));
push(@cstatus, 'Batteries(' . ($#{$c->{battery}} + 1) . '): ' . $this->join_status(\%bstatus)) if @{$c->{battery}};
my @status = join('; ', @cstatus);
my @pdata;
push(@pdata,
join('\n', @bpdata)
);
my @longout;
push(@longout,
join('\n', @blongout)
);
return unless @status;
# denote this plugin as ran ok
$this->ok;
$this->message(join(' ', @status));
$this->perfdata(join(' ', @pdata));
$this->longoutput(join(' ', @longout));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGACLI
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megaide.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGAIDE';
package App::Monitoring::Plugin::CheckRaid::Plugins::megaide;
# MegaIDE RAID controller
# Status: BROKEN: no test data
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub sudo {
my ($this) = @_;
my $cat = $this->which('cat');
"CHECK_RAID ALL=(root) NOPASSWD: $cat /proc/megaide/0/status";
}
sub check {
my $this = shift;
my $fh;
# status messages pushed here
my @status;
foreach my $f (</proc/megaide/*/status>) { # / silly comment to fix vim syntax hilighting
if (-r $f) {
open $fh, '<', $f or next;
=cut
} else {
my @CMD = ($cat, $f);
unshift(@CMD, $sudo) if $> and $sudo;
open($fh , '-|', @CMD) or next;
=cut
}
while (<$fh>) {
next unless (my($s, $n) = /Status\s*:\s*(\S+).*Logical Drive.*:\s*(\d+)/i);
next unless $this->valid($n);
if ($s ne 'ONLINE') {
$this->critical;
push(@status, "$n:$s");
} else {
push(@status, "$n:$s");
}
last;
}
close $fh;
}
return unless @status;
$this->message(join(' ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGAIDE
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megaraid.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARAID';
package App::Monitoring::Plugin::CheckRaid::Plugins::megaraid;
# MegaRAID
# Status: BROKEN: no test data
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub sudo {
my ($this) = @_;
my $cat = $this->which('cat');
my @sudo;
foreach my $mr (</proc/mega*/*/raiddrives*>) {
push(@sudo, "CHECK_RAID ALL=(root) NOPASSWD: $cat $mr") if -d $mr;
}
@sudo;
}
sub check {
my $this = shift;
# status messages pushed here
my @status;
foreach my $f (</proc/megaraid/*/raiddrives*>) { # vim/
my $fh;
if (-r $f) {
open $fh, '<', $f or next;
=cut
} else {
my @CMD = ($cat, $f);
unshift(@CMD, $sudo) if $> and $sudo;
open($fh , '-|', @CMD) or next;
=cut
}
my ($n) = $f =~ m{/proc/megaraid/([^/]+)};
while (<$fh>) {
if (my($s) = /logical drive\s*:\s*\d+.*, state\s*:\s*(\S+)/i) {
if ($s ne 'optimal') {
$this->critical;
}
push(@status, "$n: $s");
last;
}
}
close $fh;
}
return unless @status;
$this->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARAID
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/megarc.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARC';
package App::Monitoring::Plugin::CheckRaid::Plugins::megarc;
# LSI MegaRaid or Dell Perc arrays
# Check the status of all arrays on all Lsi MegaRaid controllers on the local
# machine. Uses the megarc program written by Lsi to get the status of all
# arrays on all local Lsi MegaRaid controllers.
#
# check designed from check_lsi_megaraid:
# http://www.monitoringexchange.org/cgi-bin/page.cgi?g=Detailed/2416.html;d=1
# Perl port (check_raid) by Elan Ruusamäe.
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
shift->{name};
}
sub commands {
{
'controller list' => ['-|', '@CMD', '-AllAdpInfo', '-nolog'],
'controller config' => ['-|', '@CMD', '-dispCfg', '-a$controller', '-nolog'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
(
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -AllAdpInfo -nolog",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -dispCfg -a* -nolog",
);
}
sub check {
my $this = shift;
# status messages pushed here
my @status;
# get controllers
my $fh = $this->cmd('controller list');
my @lines = <$fh>;
close $fh;
if ($lines[11] =~ /No Adapters Found/) {
$this->warning;
$this->message("No LSI adapters were found on this machine");
return;
}
my @c;
foreach (@lines[12..$#lines]) {
if (my ($id) = /^\s*(\d+)/) {
push(@c, int($id));
}
}
unless (@c) {
$this->warning;
$this->message("No LSI adapters were found on this machine");
return;
}
foreach my $c (@c) {
my $fh = $this->cmd('controller config', { '$controller' => $c });
my (%d, %s, $ld);
while (<$fh>) {
# Logical Drive : 0( Adapter: 0 ): Status: OPTIMAL
if (my($d, $s) = /Logical Drive\s+:\s+(\d+).+Status:\s+(\S+)/) {
$ld = $d;
$s{$ld} = $s;
next;
}
# SpanDepth :01 RaidLevel: 5 RdAhead : Adaptive Cache: DirectIo
if (my($s) = /RaidLevel:\s+(\S+)/) {
$d{$ld} = $s if defined $ld;
next;
}
}
close $fh;
# now process the details
unless (keys %d) {
$this->message("No arrays found on controller $c");
$this->warning;
return;
}
while (my($d, $s) = each %s) {
if ($s ne 'OPTIMAL') {
# The Array number here is incremented by one because of the
# inconsistent way that the LSI tools count arrays.
# This brings it back in line with the view in the bios
# and from megamgr.bin where the array counting starts at
# 1 instead of 0
push(@status, "Array ".(int($d) + 1)." status is ".$s{$d}." (Raid-$s on adapter $c)");
$this->critical;
next;
}
push(@status, "Logical Drive $d: $s");
}
}
return unless @status;
$this->ok->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MEGARC
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/metastat.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_METASTAT';
package App::Monitoring::Plugin::CheckRaid::Plugins::metastat;
# Solaris, software RAID
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
shift->{name};
}
sub commands {
{
'metastat' => ['>&2', '@CMD'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
"CHECK_RAID ALL=(root) NOPASSWD: $cmd"
}
sub active {
my ($this) = @_;
# return if parent said NO
my $res = $this->SUPER::active(@_);
return $res unless $res;
my $output = $this->get_metastat;
return !!@$output;
}
sub get_metastat {
my $this = shift;
# cache inside single run
return $this->{output} if defined $this->{output};
my $fh = $this->cmd('metastat');
my @data;
while (<$fh>) {
chomp;
last if /there are no existing databases/;
push(@data, $_);
}
return $this->{output} = \@data;
}
sub check {
my $this = shift;
my ($d, $sd);
# status messages pushed here
my @status;
my $output = $this->get_metastat;
foreach (@$output) {
if (/^(\S+):/) { $d = $1; $sd = ''; next; }
if (/Submirror \d+:\s+(\S+)/) { $sd = $1; next; }
if (/Device:\s+(\S+)/) { $sd = $1; next; }
if (my($s) = /State: (\S.+\w)/) {
if ($sd and $this->valid($sd) and $this->valid($d)) {
if ($s =~ /Okay/i) {
# no worries...
} elsif ($s =~ /Resync/i) {
$this->resync;
} else {
$this->critical;
}
push(@status, "$d:$sd:$s");
}
}
if (defined $d && $d =~ /hsp/) {
if (/(c[0-9]+t[0-9]+d[0-9]+s[0-9]+)\s+(\w+)/) {
$sd = $1;
my $s = $2;
$this->warning if ($s !~ /Available/);
push(@status, "$d:$sd:$s");
}
}
}
return unless @status;
$this->ok->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_METASTAT
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/mpt.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MPT';
package App::Monitoring::Plugin::CheckRaid::Plugins::mpt;
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
qw(mpt-status);
}
sub commands {
{
'get_controller_no' => ['-|', '@CMD', '-p'],
'status' => ['-|', '@CMD', '-i', '$id'],
'sync status' => ['-|', '@CMD', '-n'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
(
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -i [0-9]",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -i [1-9][0-9]",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -n",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd -p",
);
}
sub active {
my ($this) = @_;
# return if parent said NO
my $res = $this->SUPER::active(@_);
return $res unless $res;
# there should be a controller. #95
my $id = $this->get_controller;
return defined($id);
}
# get controller from mpt-status -p
# FIXME: could there be multiple controllers?
sub get_controller {
my $this = shift;
my $fh = $this->cmd('get_controller_no');
my $id;
while (<$fh>) {
chomp;
if (/^Found.*id=(\d{1,2}),.*/) {
$id = $1;
last;
}
}
close $fh;
return $id;
}
sub parse {
my ($this, $id) = @_;
my (%ld, %pd);
my $fh = $this->cmd('status', { '$id' => $id });
my %VolumeTypesHuman = (
IS => 'RAID-0',
IME => 'RAID-1E',
IM => 'RAID-1',
);
while (<$fh>) {
chomp;
# mpt-status.c __print_volume_classic
# ioc0 vol_id 0 type IM, 2 phy, 136 GB, state OPTIMAL, flags ENABLED
if (my($vioc, $vol_id, $type, $disks, $vol_size, $vol_state, $vol_flags) =
/^ioc(\d+)\s+ vol_id\s(\d+)\s type\s(\S+),\s (\d+)\sphy,\s (\d+)\sGB,\s state\s(\S+),\s flags\s(.+)/x) {
$ld{$vol_id} = {
ioc => int($vioc),
vol_id => int($vol_id),
# one of: IS, IME, IM
vol_type => $type,
raid_level => $VolumeTypesHuman{$type},
phy_disks => int($disks),
size => int($vol_size),
# one of: OPTIMAL, DEGRADED, FAILED, UNKNOWN
status => $vol_state,
# array of: ENABLED, QUIESCED, RESYNC_IN_PROGRESS, VOLUME_INACTIVE or NONE
flags => [ split ' ', $vol_flags ],
};
}
# ./include/lsi/mpi_cnfg.h
# typedef struct _RAID_PHYS_DISK_INQUIRY_DATA
# {
# U8 VendorID[8]; /* 00h */
# U8 ProductID[16]; /* 08h */
# U8 ProductRevLevel[4]; /* 18h */
# U8 Info[32]; /* 1Ch */
# }
# mpt-status.c __print_physdisk_classic
# ioc0 phy 0 scsi_id 0 IBM-ESXS PYH146C3-ETS10FN RXQN, 136 GB, state ONLINE, flags NONE
# ioc0 phy 0 scsi_id 1 ATA ST3808110AS J , 74 GB, state ONLINE, flags NONE
# ioc0 phy 0 scsi_id 1 ATA Hitachi HUA72101 AJ0A, 931 GB, state ONLINE, flags NONE
elsif (my($pioc, $num, $phy_id, $vendor, $prod_id, $rev, $size, $state, $flags) =
/^ioc(\d+)\s+ phy\s(\d+)\s scsi_id\s(\d+)\s (.{8})\s+(.{16})\s+(.{4})\s*,\s (\d+)\sGB,\s state\s(\S+),\s flags\s(.+)/x) {
$pd{$num} = {
ioc => int($pioc),
num => int($num),
phy_id => int($phy_id),
vendor => $vendor,
prod_id => $prod_id,
rev => $rev,
size => int($size),
# one of: ONLINE, MISSING, NOT_COMPATIBLE, FAILED, INITIALIZING, OFFLINE_REQUESTED, FAILED_REQUESTED, OTHER_OFFLINE, UNKNOWN
status => $state,
# array of: OUT_OF_SYNC, QUIESCED or NONE
flags => [ split ' ', $flags ],
};
} else {
warn "mpt unparsed: [$_]";
$this->unknown;
}
}
close $fh;
# extra parse, if mpt-status has -n flag, can process also resync state
# TODO: if -n becames default can do this all in one run
my $resyncing = grep {/RESYNC_IN_PROGRESS/} map { @{$_->{flags}} } values %ld;
if ($resyncing) {
my $fh = $this->cmd('sync status');
while (<$fh>) {
if (/^ioc:\d+/) {
# ignore
}
# mpt-status.c GetResyncPercentage
# scsi_id:0 70%
elsif (my($scsi_id, $percent) = /^scsi_id:(\d+) (\d+)%/) {
$pd{$scsi_id}{resync} = int($percent);
} else {
warn "mpt unparsed: [$_]";
$this->unknown;
}
}
close $fh;
}
return {
'logical' => { %ld },
'physical' => { %pd },
};
}
sub check {
my $this = shift;
# status messages pushed here
my @status;
my $id = $this->get_controller;
my $status = $this->parse($id);
# process logical units
while (my($d, $u) = each %{$status->{logical}}) {
next unless $this->valid($d);
my $s = $u->{status};
if ($s =~ /INITIAL|INACTIVE/) {
$this->warning;
} elsif ($s =~ /RESYNC/) {
$this->resync;
} elsif ($s =~ /DEGRADED|FAILED/) {
$this->critical;
} elsif ($s !~ /ONLINE|OPTIMAL/) {
$this->unknown;
}
# FIXME: this resync_in_progress is separate state of same as value in status?
if (grep { /RESYNC_IN_PROGRESS/ } @{$u->{flags}}) {
# find matching disks
my @disks = grep {$_->{ioc} eq $u->{ioc} } values %{$status->{physical}};
# collect percent for each disk
my @percent = map { $_->{resync}.'%'} @disks;
$s .= ' RESYNCING: '.join('/', @percent);
}
push(@status, "Volume $d ($u->{raid_level}, $u->{phy_disks} disks, $u->{size} GiB): $s");
}
# process physical units
while (my($d, $u) = each %{$status->{physical}}) {
my $s = $u->{status};
# remove uninteresting flags
my @flags = grep {!/NONE/} @{$u->{flags}};
# skip print if nothing in flags and disk is ONLINE
next unless @flags and $s eq 'ONLINE';
$s .= ' ' . join(' ', @flags);
push(@status, "Disk $d ($u->{size} GiB):$s");
$this->critical;
}
return unless @status;
$this->ok->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MPT
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/mvcli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MVCLI';
package App::Monitoring::Plugin::CheckRaid::Plugins::mvcli;
# Status: BROKEN: not finished
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
shift->{name};
}
sub commands {
{
'mvcli blk' => ['-|', '@CMD'],
'mvcli smart' => ['-|', '@CMD'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
"CHECK_RAID ALL=(root) NOPASSWD: $cmd"
}
sub parse_blk {
my $this = shift;
my (@blk, %blk);
my $fh = $this->cmd('mvcli blk');
while (<$fh>) {
chomp;
if (my ($blk_id) = /Block id:\s+(\d+)/) {
# block id is first item, so push previous item to list
if (%blk) {
push(@blk, { %blk });
%blk = ();
}
$blk{blk_id} = int($blk_id);
} elsif (my($pd_id) = /PD id:\s+(\d+)/) {
$blk{pd_id} = int($pd_id);
} elsif (my($vd_id) = /VD id:\s+(\d+)/) {
$blk{vd_id} = int($vd_id);
} elsif (my($bstatus) = /Block status:\s+(.+)/) {
$blk{block_status} = $bstatus;
} elsif (my($size) = /Size:\s+(\d+) K/) {
$blk{size} = int($size);
} elsif (my($offset) = /Starting offset:\s+(\d+) K/) {
$blk{offset} = int($offset);
} else {
# warn "[$_]\n";
}
}
close $fh;
if (%blk) {
push(@blk, { %blk });
}
return wantarray ? @blk : \@blk;
}
sub parse_smart {
my ($this, $blk) = @_;
# collect pd numbers
my @pd = map { $_->{pd_id} } @$blk;
my %smart;
foreach my $pd (@pd) {
my $fh = $this->cmd('mvcli smart', { '$pd' => $pd });
my %attrs = ();
while (<$fh>) {
chomp;
if (my($id, $name, $current, $worst, $treshold, $raw) = /
([\dA-F]{2})\s+ # attr
(.*?)\s+ # name
(\d+)\s+ # current
(\d+)\s+ # worst
(\d+)\s+ # treshold
([\dA-F]+) # raw
/x) {
my %attr = ();
$attr{id} = $id;
$attr{name} = $name;
$attr{current} = int($current);
$attr{worst} = int($worst);
$attr{treshold} = int($treshold);
$attr{raw} = $raw;
$attrs{$id} = { %attr };
} else {
# warn "[$_]\n";
}
}
$smart{$pd} = { %attrs };
}
return \%smart;
}
sub parse {
my $this = shift;
my $blk = $this->parse_blk;
my $smart = $this->parse_smart($blk);
return {
blk => $blk,
smart => $smart,
};
}
sub check {
my $this = shift;
my (@status);
my @d = $this->parse;
# not implemented yet
$this->unknown;
$this->message(join('; ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_MVCLI
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/sas2ircu.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SAS2IRCU';
package App::Monitoring::Plugin::CheckRaid::Plugins::sas2ircu;
# LSI SAS-2 controllers using the SAS-2 Integrated RAID Configuration Utility (SAS2IRCU)
# Based on the SAS-2 Integrated RAID Configuration Utility (SAS2IRCU) User Guide
# http://www.lsi.com/downloads/Public/Host%20Bus%20Adapters/Host%20Bus%20Adapters%20Common%20Files/SAS_SATA_6G_P12/SAS2IRCU_User_Guide.pdf
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
shift->{name};
}
sub commands {
{
'controller list' => ['-|', '@CMD', 'LIST'],
'controller status' => ['-|', '@CMD', '$controller', 'STATUS'],
'device status' => ['-|', '@CMD', '$controller', 'DISPLAY'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
(
"CHECK_RAID ALL=(root) NOPASSWD: $cmd LIST",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd * STATUS",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd * DISPLAY",
);
}
# detect controllers for sas2ircu
sub detect {
my $this = shift;
my @ctrls;
my $fh = $this->cmd('controller list');
my $success = 0;
my $state="";
my $noctrlstate="No Controllers";
while (<$fh>) {
chomp;
# Adapter Vendor Device SubSys SubSys
# Index Type ID ID Pci Address Ven ID Dev ID
# ----- ------------ ------ ------ ----------------- ------ ------
# 0 SAS2008 1000h 72h 00h:03h:00h:00h 1028h 1f1eh
if (my($c) = /^\s*(\d+)\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s*$/) {
push(@ctrls, $c);
}
$success = 1 if /SAS2IRCU: Utility Completed Successfully/;
# handle the case where there's no hardware present.
# when there is no controller, we get
# root@i41:/tmp$ /usr/sbin/sas2ircudsr LIST
# LSI Corporation SAS2 IR Configuration Utility.
# Version 18.00.00.00 (2013.11.18)
# Copyright (c) 2009-2013 LSI Corporation. All rights reserved.
# SAS2IRCU: MPTLib2 Error 1
# root@i41:/tmp$ echo $?
# 1
if (/SAS2IRCU: MPTLib2 Error 1/) {
$state = $noctrlstate;
$success = 1 ;
}
}
unless (close $fh) {
#sas2ircu exits 1 (but close exits 256) when we close fh if we have no controller, so handle that, too
if ($? != 256 && $state eq $noctrlstate) {
$this->critical;
}
}
unless ($success) {
$this->critical;
}
return wantarray ? @ctrls : \@ctrls;
}
sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
sub ltrim { my $s = shift; $s =~ s/^\s+//; return $s };
sub rtrim { my $s = shift; $s =~ s/\s+$//; return $s };
sub check {
my $this = shift;
my @ctrls = $this->detect;
my @status;
my $numvols=0;
# determine the RAID states of each controller
foreach my $c (@ctrls) {
my $fh = $this->cmd('controller status', { '$controller' => $c });
my $novolsstate="No Volumes";
my $state;
my $success = 0;
while (<$fh>) {
chomp;
# match adapter lines
if (my($s) = /^\s*Volume state\s*:\s*(\w+)\s*$/) {
$state = $s;
$numvols++;
if ($state ne "Optimal") {
$this->critical;
}
}
$success = 1 if /SAS2IRCU: Utility Completed Successfully/;
##handle the case where there are no volumes configured
#
# SAS2IRCU: there are no IR volumes on the controller!
# SAS2IRCU: Error executing command STATUS.
if (/SAS2IRCU: there are no IR volumes on the controller/
or /The STATUS command is not supported by the firmware currently loaded on controller/
) {
# even though this isn't the last line, go ahead and set success.
$success = 1;
$state = $novolsstate;
}
}
unless (close $fh) {
#sas2ircu exits 256 when we close fh if we have no volumes, so handle that, too
if ($? != 256 && $state eq $novolsstate) {
$this->critical;
$state = $!;
}
}
unless ($success) {
$this->critical;
$state = "SAS2IRCU Unknown exit";
}
unless ($state) {
$state = "Unknown Error";
}
my $finalvolstate=$state;
#push(@status, "ctrl #$c: $numvols Vols: $state");
##### now look at the devices.
# Device is a Hard disk
# Enclosure # : 2
# Slot # : 0
# SAS Address : 500065b-3-6789-abe0
# State : Ready (RDY)
# Size (in MB)/(in sectors) : 3815447/7814037167
# Manufacturer : ATA
# Model Number : ST4000DM000-1F21
# Firmware Revision : CC52
# Serial No : S30086G4
# GUID : 5000c5006d27b344
# Protocol : SATA
# Drive Type : SATA_HDD
$fh = $this->cmd('device status', { '$controller' => $c });
$state="";
$success = 0;
my $enc="";
my $slot="";
my @data;
my $device="";
my $numslots=0;
my $finalstate;
my $finalerrors="";
while (my $line = <$fh>) {
chomp $line;
# Device is a Hard disk
# Device is a Hard disk
# Device is a Enclosure services device
#
#lets make sure we're only checking disks. we dont support other devices right now
if ("$line" eq 'Device is a Hard disk') {
$device='disk';
} elsif ($line =~ /^Device/) {
$device='other';
}
if ("$device" eq 'disk') {
if ($line =~ /Enclosure #|Slot #|State /) {
#find our enclosure #
if ($line =~ /^ Enclosure # /) {
@data = split /:/, $line;
$enc=trim($data[1]);
#every time we hit a new enclosure line, reset our state and slot
undef $state;
undef $slot;
}
#find our slot #
if ($line =~ /^ Slot # /) {
@data = split /:/, $line;
$slot=trim($data[1]);
$numslots++
}
#find our state
if ($line =~ /^ State /) {
@data = split /:/, $line;
$state=ltrim($data[1]);
#for test
#if ($numslots == 10 ) { $state='FREDFISH';}
#when we get a state, test on it and report it..
if ($state =~ /Optimal|Ready/) {
#do nothing at the moment.
} else {
$this->critical;
$finalstate=$state;
$finalerrors="$finalerrors ERROR:Ctrl$c:Enc$enc:Slot$slot:$state";
}
}
}
}
if ($line =~ /SAS2IRCU: Utility Completed Successfully/) {
$success = 1;
}
} #end while
unless (close $fh) {
$this->critical;
$state = $!;
}
unless ($success) {
$this->critical;
$state = "SAS2IRCU Unknown exit";
}
unless ($state) {
$state = "Unknown Error";
}
unless($finalstate) {
$finalstate=$state;
}
#per controller overall report
#push(@status, ":$numslots Drives:$finalstate:$finalerrors");
push(@status, "ctrl #$c: $numvols Vols: $finalvolstate: $numslots Drives: $finalstate:$finalerrors:");
}
##if we didn't get a status out of the controllers and an empty ctrls array, we must not have any.
unless (@status && @ctrls) {
push(@status, "No Controllers");
}
return unless @status;
$this->ok->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SAS2IRCU
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/smartctl.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SMARTCTL';
package App::Monitoring::Plugin::CheckRaid::Plugins::smartctl;
# NOTE: not standalone plugin
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
use strict;
use warnings;
sub program_names {
shift->{name};
}
sub commands {
{
'smartctl' => ['-|', '@CMD', '-H', '$dev', '$diskopt$disk'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
# nothing, as not standalone plugin yet
}
# check for -H parameter for physical disks
# this is currently called out from cciss plugin
# @param device list
# device list being an array of:
# - device to check (/dev/cciss/c0d0)
# - disk options (-dcciss)
# - disk number (0..15)
sub check_devices {
my $this = shift;
my @devs = @_;
unless (@devs) {
$this->warning;
$this->message("No devices to check");
return;
}
# status message for devs, latter just joined for shorter messages
my %status;
foreach my $ref (@devs) {
my ($dev, $diskopt, $disk) = @$ref;
my $fh = $this->cmd('smartctl', { '$dev' => $dev, '$diskopt' => $diskopt => '$disk' => $disk });
while (<$fh>) {
chomp;
# SMART Health Status: HARDWARE IMPENDING FAILURE GENERAL HARD DRIVE FAILURE [asc=5d, ascq=10]
if (my($s, $sc) = /SMART Health Status: (.*?)(\s*\[asc=\w+, ascq=\w+\])?$/) {
# use shorter output, message that hpacucli would use
if ($s eq 'HARDWARE IMPENDING FAILURE GENERAL HARD DRIVE FAILURE') {
$s = 'Predictive Failure';
}
if ($s eq 'Predictive Failure') {
$this->warning;
} elsif ($s !~ '^OK') {
$this->critical;
}
push(@{$status{$s}}, $dev.'#'.$disk);
}
}
close($fh);
}
return unless %status;
$this->ok->message($this->join_status(\%status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SMARTCTL
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Plugins/tw_cli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_TW_CLI';
package App::Monitoring::Plugin::CheckRaid::Plugins::tw_cli;
# tw_cli(8) is a Command Line Interface Storage Management Software for
# AMCC/3ware ATA RAID Controller(s).
# Owned by LSI currently: https://en.wikipedia.org/wiki/3ware
#
# http://www.cyberciti.biz/files/tw_cli.8.html
use base 'App::Monitoring::Plugin::CheckRaid::Plugin';
# not yet, see:
# https://github.com/glensc/nagios-plugin-check_raid/pull/131#issuecomment-189957806
#use Date::Parse qw(strptime);
#use DateTime;
use strict;
use warnings;
sub program_names {
qw(tw_cli-9xxx tw_cli tw-cli);
}
sub commands {
{
'show' => ['-|', '@CMD', 'show'], # This is 'info' output AND enclosure summary
'unitstatus' => ['-|', '@CMD', 'info', '$controller', 'unitstatus'],
'drivestatus' => ['-|', '@CMD', 'info', '$controller', 'drivestatus'],
'bbustatus' => ['-|', '@CMD', 'info', '$controller', 'bbustatus'],
'enc_show_all' => ['-|', '@CMD', '$encid', 'show all'],
}
}
sub sudo {
my ($this, $deep) = @_;
# quick check when running check
return 1 unless $deep;
my $cmd = $this->{program};
(
"CHECK_RAID ALL=(root) NOPASSWD: $cmd info",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd info *",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd show",
"CHECK_RAID ALL=(root) NOPASSWD: $cmd * show all",
);
}
sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
sub to_i {
my $i = shift;
return $i if $i !~ /^\d+$/;
return int($i);
}
sub parse {
my $this = shift;
my (%c);
# scan controllers
my ($sect_ctl, $sect_enc) = 0;
my $fh = $this->cmd('show');
while (<$fh>) {
# Section break
if(/^\s*$/) { ($sect_ctl,$sect_enc) = (0,0); next; };
# header line
if(/^-+$/) { next; };
# section headers: Controller
# Ctl Model Ports Drives Units NotOpt RRate VRate BBU
# Ctl Model (V)Ports Drives Units NotOpt RRate VRate BBU
if (/^Ctl.*Model.*Rate/) { $sect_ctl = 1; next; };
# section headers: Enclosure
# Encl Slots Drives Fans TSUnits Ctls
# Encl Slots Drives Fans TSUnits PSUnits
# Enclosure Slots Drives Fans TSUnits PSUnits Alarms
if (/^Encl.*Drive/) { $sect_enc = 1; next; };
# controller section
if ($sect_ctl and my($ctl, $model, $ports, $drives, $units, $notopt, $rrate, $vrate, $bbu) = m{^
(c\d+)\s+ # Controller
(\S+)\s+ # Model
(\d+)\s+ # (V)Ports
(\d+)\s+ # Drives
(\d+)\s+ # Units
(\d+)\s+ # NotOpt: Not Optional
# Not Optimal refers to any state except OK and VERIFYING.
# Other states include INITIALIZING, INIT-PAUSED,
# REBUILDING, REBUILD-PAUSED, DEGRADED, MIGRATING,
# MIGRATE-PAUSED, RECOVERY, INOPERABLE, and UNKNOWN.
(\d+)\s+ # RRate: Rebuild Rate
(\d+|-)\s+ # VRate: Verify Rate
(\S+|-)? # BBU
}x) {
$c{$ctl} = {
model => $model,
ports => int($ports),
drives => int($drives),
units => int($units),
optimal => int(!$notopt),
rrate => int($rrate),
vrate => to_i($vrate),
bbu => $bbu,
};
}
# enclosure section
if ($sect_enc and my($enc, $slots, $drives, $fans, $tsunits, $psunits, $alarms) = m{^
((?:/c\d+)?/e\d+)\s+ # Controller, Enclosure
# 9650SE reports enclosures as /eX
# 9690SA+ report enclosures as /cX/eX
(\d+)\s+ # Slots
(\d+)\s+ # Drives
(\d+)\s+ # Fans
(\d+)\s+ # TSUnits - Temp Sensor
(\d+)?\s+ # PSUnits - Power Supply, not always present!
(\d+)?\s+ # Controller OR Alarms, not always present!
}x) {
# This will be filled in later by the enclosure pass
$c{$enc} = {};
}
}
close $fh;
# no controllers? skip early
return unless %c;
for my $c (grep /^\/?c\d+$/, keys %c) {
# get each unit on controllers
$fh = $this->cmd('unitstatus', { '$controller' => $c });
while (<$fh>) {
if (my($u, $type, $status, $p_rebuild, $p_vim, $strip, $size, $cache, $avrify) = m{^
(u\d+)\s+ # Unit
(\S+)\s+ # UnitType
(\S+)\s+ # Status
(\S+)\s+ # %RCmpl: The %RCompl reports the percent completion
# of the unit's Rebuild, if this task is in progress.
(\S+)\s+ # %V/I/M: The %V/I/M reports the percent completion
# of the unit's Verify, Initialize, or Migrate,
# if one of these are in progress.
(\S+)\s+ # Strip
(\S+)\s+ # Size(GB)
(\S+)\s+ # Cache
(\S+) # AVrify
}x) {
$c{$c}{unitstatus}{$u} = {
type => $type,
status => $status,
rebuild_percent => $p_rebuild,
vim_percent => $p_vim,
strip => $strip,
size => $size,
cache => $cache,
avrify => $avrify,
};
next;
}
if (m{^u\d+}) {
$this->unknown;
warn "unparsed: [$_]";
}
}
close $fh;
# get individual disk status
$fh = $this->cmd('drivestatus', { '$controller' => $c });
# common regexp
my $r = qr{^
(p\d+)\s+ # Port
(\S+)\s+ # Status
(\S+)\s+ # Unit
([\d.]+\s[TG]B|-)\s+ # Size
}x;
while (<$fh>) {
# skip empty line
next if /^$/;
# Detect version
if (/^Port/) {
# <=9.5.1: Blocks Serial
$r .= qr{
(\S+)\s+ # Blocks
(.+) # Serial
}x;
next;
} elsif (/^VPort/) {
# >=9.5.2: Type Phy Encl-Slot Model
$r .= qr{
(\S+)\s+ # Type
(\S+)\s+ # Phy
(\S+)\s+ # Encl-Slot
(.+) # Model
}x;
next;
}
if (my($port, $status, $unit, $size, @rest) = ($_ =~ $r)) {
# do not report disks not present
# tw_cli 9.5.2 and above do not list these at all
next if $status eq 'NOT-PRESENT';
my %p;
if (@rest <= 2) {
my ($blocks, $serial) = @rest;
%p = (
blocks => to_i($blocks),
serial => trim($serial),
);
} else {
my ($type, $phy, $encl, $model) = @rest;
%p = (
type => $type,
phy => to_i($phy),
encl => $encl,
model => $model,
);
}
$c{$c}{drivestatus}{$port} = {
status => $status,
unit => $unit,
size => $size,
%p,
};
next;
}
if (m{^p\d+}) {
$this->unknown;
warn "unparsed: [$_]";
}
}
close $fh;
# get BBU status
$fh = $this->cmd('bbustatus', { '$controller' => $c });
while (<$fh>) {
next if /^$/;
next if /^-{10,}$/;
if (my($bbu, $onlinestate, $bbuready, $status, $volt, $temp, $hours, $lastcaptest) = m{^
(bbu\d*)\s+ # BBU, possibly numbered (RARE)
(\S+)\s+ # OnlineState
(\S+)\s+ # BBUReady
(\S+)\s+ # Status
(\S+)\s+ # Volt
(\S+)\s+ # Temp
(\d+)\s+ # Hours
(\S+)\s+ # LastCapTest
}x) {
$c{$c}{bbustatus}{$bbu} = {
OnlineState => $onlinestate,
BBUReady => $bbuready,
Status => $status,
Volt => $volt,
Temp => $temp,
Hours => $hours,
LastCapTest => $lastcaptest,
};
next;
}
if (m{^\S+\+}) {
$this->unknown;
warn "unparsed: [$_]";
}
}
close $fh;
}
# Do enclosures now, which might NOT be attached the controllers
# WARNING: This data section has not always been consistent over versions of tw_cli.
# You should try to use the newest version of the driver, as it deliberately uses the newer style of output
# rather than the output for the tw_cli versions released with 9550SX/9590SE/9650SE
for my $encid (grep /\/e\d+$/, keys %c) {
$fh = $this->cmd('enc_show_all', { '$encid' => $encid });
# Variable names chose to be 'sect_XXX' explicitly.
# This says what section we are in right now
my ($sect_enc, $sect_fan, $sect_tmp, $sect_psu, $sect_slt, $sect_alm) = (0,0,0,0,0,0);
# This says what section we have seen, it gets reset at the start of each enclosure block;
my ($seen_enc, $seen_fan, $seen_tmp, $seen_psu, $seen_slt, $seen_alm) = (0,0,0,0,0,0);
while (<$fh>) {
# Skip the header break lines
next if /^-+$/;
# and the partial indented header that is ABOVE the fan header
next if /^\s+-+Speed-+\s*$/;
# If the line is blank, reset our section headers
if(/^\s*$/){
($sect_enc, $sect_fan, $sect_tmp, $sect_psu, $sect_slt, $sect_alm) = (0,0,0,0,0,0);
# If we have SEEN all of the sections, also reset the seen markers
# This is needed when the output contains multiple enclosures
if($sect_enc and $sect_fan and $sect_tmp and $sect_psu and $sect_slt and $sect_alm) {
($seen_enc, $seen_fan, $seen_tmp, $seen_psu, $seen_slt, $seen_alm) = (0,0,0,0,0,0);
}
next;
}
if (/^Encl.*Status/) { $seen_enc = $sect_enc = 1; next; }
if (/^Fan.*Status/) { $seen_fan = $sect_fan = 1; next; }
if (/^TempSensor.*Status/) { $seen_tmp = $sect_tmp = 1; next; }
if (/^PowerSupply.*Status/) { $seen_psu = $sect_psu = 1; next; }
if (/^Slot.*Status/) { $seen_slt = $sect_slt = 1; next; }
if (/^Alarm.*Status/) { $seen_alm = $sect_alm = 1; next; }
# ------ Start of new enclosure
if ($sect_enc and my($encl, $encl_status) = m{^
((?:/c\d+)?/e\d+)\s+ # Controller, Enclosure
(\S+)\s+ # Status
}x) {
# This is a special case for the test environment, as it is
# hard to feed MULTI command inputs into the mock.
if($ENV{'HARNESS_ACTIVE'} and $encl ne $encid) {
$encid = $encl;
}
$c{$encid} = {
encl => $encl, # Dupe of $encid to verify
status => $encl_status,
# This is the top-level enclosure object
fans => {},
tempsensor => {},
powersupply => {},
slot => {},
alarm => {},
};
}
# ------ Fans
elsif ($sect_fan and my($fan, $fan_status, $fan_state, $fan_step, $fan_rpm, $fan_identify) = m{^
(fan\S+)\s+ # Fan
(\S+)\s+ # Status
(\S+)\s+ # State
(\S+)\s+ # Step
(\d+|N/A)\s+ # RPM
(\S+)\s+ # Identify
}x) {
$c{$encid}{fans}{$fan} = {
status => $fan_status,
state => $fan_state,
step => $fan_step,
rpm => $fan_rpm,
identify => $fan_identify,
};
next;
}
# ------ TempSensor
elsif ($sect_tmp and my($tmp, $tmp_status, $tmp_temperature, $tmp_identify) = m{^
(temp\S+)\s+ # TempSensor
(\S+)\s+ # Status
(\S+)\s+ # Temperature
(\S+)\s+ # Identify
}x) {
$c{$encid}{tempsensor}{$tmp} = {
status => $tmp_status,
temperature => $tmp_temperature,
identify => $tmp_identify,
};
next;
}
# ------ PowerSupply
elsif ($sect_psu and my($psu, $psu_status, $psu_state, $psu_voltage, $psu_current, $psu_identify) = m{^
((?:pw|psu)\S+)\s+ # PowerSupply
(\S+)\s+ # Status
(\S+)\s+ # State
(\S+)\s+ # Voltage
(\S+)\s+ # Current
(\S+)\s+ # Identify
}x) {
$c{$encid}{powersupply}{$psu} = {
status => $psu_status,
state => $psu_state,
voltage => $psu_voltage,
current => $psu_current,
identify => $psu_identify,
};
next;
}
# ------ Slot
elsif ($sect_slt and my($slt, $slt_status, $slt_vport, $slt_identify) = m{^
(slo?t\S+)\s+ # Slot
(\S+)\s+ # Status
(\S+)\s+ # (V)Port
(\S+)\s+ # Identify
}x) {
$c{$encid}{slot}{$slt} = {
status => $slt_status,
vport => $slt_vport,
identify => $slt_identify,
};
next;
}
# ------ Alarm
elsif ($sect_alm and my($alm, $alm_status, $alm_state, $alm_audibility) = m{^
(alm\S+)\s+ # Alarm
(\S+)\s+ # Status
(\S+)\s+ # State
(\S+)\s+ # Audibility
}x) {
$c{$encid}{alarm}{$alm} = {
status => $alm_status,
state => $alm_state,
audibility => $alm_audibility,
};
next;
}
# ---- End of known data
elsif (m{^\S+\+}) {
$this->unknown;
warn "unparsed: [$_]";
}
}
close $fh;
}
return \%c;
}
sub check {
my $this = shift;
# status messages pushed here
my @status;
my $c = $this->parse;
if (!$c) {
$this->unknown;
$this->message("No Adapters were found on this machine");
}
# process each controller
for my $cid (sort grep !/e\d+/, keys %$c) {
my $c = $c->{$cid};
my @cstatus;
for my $uid (sort keys %{$c->{unitstatus}}) {
my $u = $c->{unitstatus}->{$uid};
my $s = $u->{status};
if ($s =~ /INITIALIZING|MIGRATING/) {
$this->warning;
$s .= " $u->{vim_percent}";
} elsif ($s eq 'VERIFYING') {
$this->check_status;
$s .= " $u->{vim_percent}";
} elsif ($s eq 'REBUILDING') {
$this->resync;
$s .= " $u->{rebuild_percent}";
} elsif ($s eq 'DEGRADED') {
$this->critical;
} elsif ($s ne 'OK') {
$this->critical;
}
my @ustatus = $s;
# report cache, no checking
if ($u->{cache} && $u->{cache} ne '-') {
push(@ustatus, "Cache:$u->{cache}");
}
push(@status, "$cid($c->{model}): $uid($u->{type}): ".join(', ', @ustatus));
}
# check individual disk status
my %ds;
foreach my $p (sort { $a cmp $b } keys %{$c->{drivestatus}}) {
my $d = $c->{drivestatus}->{$p};
my $ds = $d->{status};
if ($ds eq 'VERIFYING') {
$this->check_status;
} elsif ($ds ne 'OK') {
$this->critical;
}
if ($d->{unit} eq '-') {
$ds = 'SPARE';
}
push(@{$ds{$ds}}, $p);
}
push(@status, "Drives($c->{drives}): ".$this->join_status(\%ds)) if %ds;
# check BBU, but be prepared that BBU status might not report anything
if ($this->{options}{bbu_monitoring} && $c->{bbu} && $c->{bbu} ne '-') {
# On old controllers, bbustatus did not exist; and the only BBU status
# you got was on the controller listing.
if(scalar(keys %{$c->{bbustatus}}) < 1) {
$this->critical if $c->{bbu} ne 'OK';
push(@status, "BBU: $c->{bbu}");
} else {
foreach my $bbuid (sort { $a cmp $b } keys %{$c->{bbustatus}}) {
my $bat = $c->{bbustatus}->{$bbuid};
my $bs = $bat->{Status}; # We might override this later
my @batmsg;
if($bs eq 'Testing' or $bs eq 'Charging') {
$this->bbulearn;
} elsif($bs eq 'WeakBat') {
# Time to replace your battery
$this->warning;
} elsif($bs ne 'OK') {
$this->critical;
}
# We do NOT check BBUReady, as it doesn't private granular
# info.
# Check OnlineState flag as well
# A battery can be GOOD, but disabled; this is only reflected in OnlineState.
if($bat->{OnlineState} ne 'On') {
push @batmsg, 'OnlineStatus='.$bat->{OnlineState};
$this->critical;
}
# Check voltage & temps
push @batmsg, 'Volt='.$bat->{Volt};
push @batmsg, 'Temp='.$bat->{Temp};
if ($bat->{Volt} =~ /^(LOW|HIGH)$/) {
$this->critical;
} elsif ($bat->{Volt} =~ /^(LOW|HIGH)$/) {
$this->warning;
}
if ($bat->{Temp} =~ /^(LOW|HIGH)$/) {
$this->critical;
} elsif ($bat->{Temp} =~ /^(LOW|HIGH)$/) {
$this->warning;
}
# Check runtime estimate
# Warn if too low
my $bbulearn = '';
if ($bat->{Hours} ne '-' and int($bat->{Hours}) <= 1) {
# TODO: make this configurable before going live
#$this->warning;
$this->bbulearn;
$bbulearn = '/LEARN';
}
push @batmsg, 'Hours='.$bat->{Hours};
# Check date of last capacity test
if ($bat->{LastCapTest} eq 'xx-xxx-xxxx') {
$this->bbulearn;
$bbulearn = '/LEARN';
} elsif ($bat->{LastCapTest} ne '-') {
# TODO: is the short name of month localized by tw_cli?
#my ($mday, $mon, $year) = (strptime($bat->{LastCapTest}, '%d-%b-%Y'))[3,4,5];
#my $lastcaptest_epoch = DateTime->new(year => $year, month => $mon, day => $mday, hour => 0, minute => 0, second => 0);
#my $present_time = time;
## TODO: this value should be configurable before going live, also need to mock system date for testing
#if (($present_time-$lastcaptest_epoch) > 86400*365) {
# $this->bbulearn;
#}
}
push @batmsg, 'LastCapTest='.$bat->{LastCapTest};
my $msg = join(',', @batmsg);
my $bbustatus = $bs.$bbulearn;
$bbustatus = "$bbuid=$bs" if $bbuid ne 'bbu'; # If we have multiple BBU, specify which one
push(@status, "BBU: $bbustatus($msg)");
}
}
}
}
# process each enclosure
for my $eid (sort grep /\/e\d+/, keys %$c) {
my $e = $c->{$eid};
# If the enclosure command returned nothing, we have no status to
# report.
next unless defined($e->{status});
# Something is wrong, but we are not sure what yet.
$this->warning unless $e->{status} eq 'OK';
my @estatus;
for my $fan_id (sort keys %{$e->{fans}}) {
my $f = $e->{fans}->{$fan_id};
my $s = $f->{status};
next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
$this->warning if $s ne 'OK';
push(@estatus, "$fan_id=$s($f->{rpm})");
}
for my $tmp_id (sort keys %{$e->{tempsensor}}) {
my $t = $e->{tempsensor}->{$tmp_id};
my $s = $t->{status};
next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
$this->warning if $s ne 'OK';
$t->{temperature} =~ s/\(\d+F\)//; # get rid of extra units
push(@estatus, "$tmp_id=$s($t->{temperature})");
}
for my $psu_id (sort keys %{$e->{powersupply}}) {
my $t = $e->{powersupply}->{$psu_id};
my $s = $t->{status};
next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
$this->warning if $s ne 'OK';
push(@estatus, "$psu_id=$s(status=$t->{state},voltage=$t->{voltage},current=$t->{current})");
}
for my $slot_id (sort keys %{$e->{slot}}) {
my $t = $e->{slot}->{$slot_id};
my $s = $t->{status};
next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
$this->warning if $s ne 'OK';
push(@estatus, "$slot_id=$s");
}
for my $alarm_id (sort keys %{$e->{alarm}}) {
my $t = $e->{alarm}->{$alarm_id};
my $s = $t->{status};
next if $s eq 'NOT-REPORTABLE' or $s eq 'NOT-INSTALLED' or $s eq 'NO-DEVICE';
$this->warning if $s ne 'OK';
push(@estatus, "$alarm_id=$s(State=$t->{state},Audibility=$t->{audibility})");
}
#warn join("\n", @estatus);
push(@status, "Enclosure: $eid(".join(',', @estatus).")");
}
return unless @status;
$this->ok->message(join(', ', @status));
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_TW_CLI
$fatpacked{"App/Monitoring/Plugin/CheckRaid/SerialLine.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_SERIALLINE';
package App::Monitoring::Plugin::CheckRaid::SerialLine;
# Package dealing with connecting to serial line and handling UUCP style locks.
use Carp;
use strict;
use warnings;
sub new {
my $self = shift;
my $class = ref($self) || $self;
my $device = shift;
my $this = {
lockdir => "/var/lock",
@_,
lockfile => undef,
device => $device,
fh => undef,
};
bless($this, $class);
}
sub lock {
my $self = shift;
# create lock in style: /var/lock/LCK..ttyS0
my $device = shift;
my ($lockfile) = $self->{device} =~ m#/dev/(.+)#;
$lockfile = "$self->{lockdir}/LCK..$lockfile";
if (-e $lockfile) {
return 0;
}
open(my $fh, '>', $lockfile) || croak "Can't create lock: $lockfile\n";
print $fh $$;
close($fh);
$self->{lockfile} = $lockfile;
}
sub open {
my $self = shift;
$self->lock or return;
# open the device
open(my $fh, '+>', $self->{device}) || croak "Couldn't open $self->{device}, $!\n";
$self->{fh} = $fh;
}
sub close {
my $self = shift;
if ($self->{fh}) {
close($self->{fh});
undef($self->{fh});
unlink $self->{lockfile} or carp $!;
}
}
sub DESTROY {
my $self = shift;
$self->close();
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_SERIALLINE
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Sudoers.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_SUDOERS';
package App::Monitoring::Plugin::CheckRaid::Sudoers;
use App::Monitoring::Plugin::CheckRaid::Utils;
use warnings;
use strict;
use Exporter 'import';
our @EXPORT = qw(sudoers);
our @EXPORT_OK = @EXPORT;
# update sudoers file
#
# if sudoers config has "#includedir" directive, add file to that dir
# otherwise update main sudoers file
sub sudoers {
my $dry_run = shift;
my @plugins = @_;
# build values to be added
# go over all active plugins
my @sudo;
foreach my $plugin (@plugins) {
# collect sudo rules
my @rules = $plugin->sudo(1) or next;
push(@sudo, @rules);
}
unless (@sudo) {
warn "Your configuration does not need to use sudo, sudoers not updated\n";
return;
}
my @rules = join "\n", (
"",
# setup alias, so we could easily remove these later by matching lines with 'CHECK_RAID'
# also this avoids installing ourselves twice.
"# Lines matching CHECK_RAID added by $0 -S on ". scalar localtime,
"User_Alias CHECK_RAID=nagios",
"Defaults:CHECK_RAID !requiretty",
# actual rules from plugins
join("\n", @sudo),
"",
);
if ($dry_run) {
warn "Content to be inserted to sudo rules:\n";
warn "--- sudoers ---\n";
print @rules;
warn "--- sudoers ---\n";
return;
}
my $sudoers = find_file('/usr/local/etc/sudoers', '/etc/sudoers');
my $visudo = which('visudo');
die "Unable to find sudoers file.\n" unless -f $sudoers;
die "Unable to write to sudoers file '$sudoers'.\n" unless -w $sudoers;
die "visudo program not found\n" unless -x $visudo;
# parse sudoers file for "#includedir" directive
my $sudodir = parse_sudoers_includedir($sudoers);
if ($sudodir) {
# sudo will read each file in /etc/sudoers.d, skipping file names that
# end in ~ or contain a . character to avoid causing problems with
# package manager or editor temporary/backup files
$sudoers = "$sudodir/check_raid";
}
warn "Updating file $sudoers\n";
# NOTE: secure as visudo itself: /etc is root owned
my $new = $sudoers.".new.".$$;
# setup to have sane perm for new sudoers file
umask(0227);
open my $fh, '>', $new or die $!;
# insert old sudoers
if (!$sudodir) {
open my $old, '<', $sudoers or die $!;
while (<$old>) {
print $fh $_;
}
close $old or die $!;
}
# insert the rules
print $fh @rules;
close $fh;
# validate sudoers
system($visudo, '-c', '-f', $new) == 0 or unlink($new),exit $? >> 8;
# check if they differ
if (filediff($sudoers, $new)) {
# use the new file
rename($new, $sudoers) or die $!;
warn "$sudoers file updated.\n";
} else {
warn "$sudoers file not changed.\n";
unlink($new);
}
}
# return first "#includedir" directive from $sudoers file
sub parse_sudoers_includedir {
my ($sudoers) = @_;
open my $fh, '<', $sudoers or die "Can't open: $sudoers: $!";
while (<$fh>) {
if (my ($dir) = /^#includedir\s+(.+)$/) {
return $dir;
}
}
close $fh or die $!;
return undef;
}
# return FALSE if files are identical
# return TRUE if files are different
# return TRUE if any of the files is missing
sub filediff {
my ($file1, $file2) = @_;
# return TRUE if neither of them exist
return 1 unless -f $file1;
return 1 unless -f $file2;
my $f1 = cat($file1);
my $f2 = cat($file2);
# wipe comments
$f1 =~ s/^#.+$//m;
$f2 =~ s/^#.+$//m;
# return TRUE if they differ
return $f1 ne $f2;
}
# get contents of a file
sub cat {
my ($file) = @_;
open(my $fh, '<', $file) or die "Can't open $file: $!";
local $/ = undef;
local $_ = <$fh>;
close($fh) or die $!;
return $_;
}
# find first existing file from list of file paths
sub find_file {
for my $file (@_) {
return $file if -f $file;
}
return undef;
}
APP_MONITORING_PLUGIN_CHECKRAID_SUDOERS
$fatpacked{"App/Monitoring/Plugin/CheckRaid/Utils.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_UTILS';
package App::Monitoring::Plugin::CheckRaid::Utils;
use warnings;
use strict;
use Exporter 'import';
our @EXPORT = qw(which find_sudo);
our @EXPORT_OK = @EXPORT;
# registered plugins
our @plugins;
# devices to ignore
our @ignore;
# debug level
our $debug = 0;
# paths for which()
our @paths = split /:/, $ENV{'PATH'};
unshift(@paths, qw(/usr/local/nrpe /usr/local/bin /sbin /usr/sbin /bin /usr/sbin /opt/bin /opt/MegaRAID/MegaCli /usr/StorMan));
# lookup program from list of possible filenames
# search is performed from $PATH plus additional hardcoded @paths
# NOTE: we do not check for execute bit as it may fail for non-root. #104
sub which {
for my $prog (@_) {
for my $path (@paths) {
return "$path/$prog" if -f "$path/$prog";
}
}
return undef;
}
our @sudo;
sub find_sudo {
# no sudo needed if already root
return [] unless $>;
# detect once
return \@sudo if @sudo;
my $sudo = which('sudo') or die "Can't find sudo";
push(@sudo, $sudo);
# detect if sudo supports -A, issue #88
use IPC::Open3;
my $fh;
my @cmd = ($sudo, '-h');
my $pid = open3(undef, $fh, undef, @cmd) or die "Can't run 'sudo -h': $!";
local $/ = undef;
local $_ = <$fh>;
close($fh) or die $!;
push(@sudo, '-A') if /-A/;
return \@sudo;
}
1;
APP_MONITORING_PLUGIN_CHECKRAID_UTILS
$fatpacked{"Class/Accessor.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CLASS_ACCESSOR';
package Class::Accessor;require 5.00502;use strict;$Class::Accessor::VERSION='0.34';sub new {my($proto,$fields)=@_;my($class)=ref$proto || $proto;$fields={}unless defined$fields;bless {%$fields},$class}sub mk_accessors {my($self,@fields)=@_;$self->_mk_accessors('rw',@fields)}if (eval {require Sub::Name}){Sub::Name->import}{no strict 'refs';sub import {my ($class,@what)=@_;my$caller=caller;for (@what){if (/^(?:antlers|moose-?like)$/i){*{"${caller}::has"}=sub {my ($f,%args)=@_;$caller->_mk_accessors(($args{is}||"rw"),$f)};*{"${caller}::extends"}=sub {@{"${caller}::ISA"}=@_;unless (grep $_->can("_mk_accessors"),@_){push @{"${caller}::ISA"},$class}};&{"${caller}::extends"}(@{"${caller}::ISA"})}}}sub follow_best_practice {my($self)=@_;my$class=ref$self || $self;*{"${class}::accessor_name_for"}=\&best_practice_accessor_name_for;*{"${class}::mutator_name_for"}=\&best_practice_mutator_name_for}sub _mk_accessors {my($self,$access,@fields)=@_;my$class=ref$self || $self;my$ra=$access eq 'rw' || $access eq 'ro';my$wa=$access eq 'rw' || $access eq 'wo';for my$field (@fields){my$accessor_name=$self->accessor_name_for($field);my$mutator_name=$self->mutator_name_for($field);if($accessor_name eq 'DESTROY' or $mutator_name eq 'DESTROY'){$self->_carp("Having a data accessor named DESTROY in '$class' is unwise.")}if ($accessor_name eq $mutator_name){my$accessor;if ($ra && $wa){$accessor=$self->make_accessor($field)}elsif ($ra){$accessor=$self->make_ro_accessor($field)}else {$accessor=$self->make_wo_accessor($field)}my$fullname="${class}::$accessor_name";my$subnamed=0;unless (defined &{$fullname}){subname($fullname,$accessor)if defined&subname;$subnamed=1;*{$fullname}=$accessor}if ($accessor_name eq $field){my$alias="${class}::_${field}_accessor";subname($alias,$accessor)if defined&subname and not $subnamed;*{$alias}=$accessor unless defined &{$alias}}}else {my$fullaccname="${class}::$accessor_name";my$fullmutname="${class}::$mutator_name";if ($ra and not defined &{$fullaccname}){my$accessor=$self->make_ro_accessor($field);subname($fullaccname,$accessor)if defined&subname;*{$fullaccname}=$accessor}if ($wa and not defined &{$fullmutname}){my$mutator=$self->make_wo_accessor($field);subname($fullmutname,$mutator)if defined&subname;*{$fullmutname}=$mutator}}}}}sub mk_ro_accessors {my($self,@fields)=@_;$self->_mk_accessors('ro',@fields)}sub mk_wo_accessors {my($self,@fields)=@_;$self->_mk_accessors('wo',@fields)}sub best_practice_accessor_name_for {my ($class,$field)=@_;return "get_$field"}sub best_practice_mutator_name_for {my ($class,$field)=@_;return "set_$field"}sub accessor_name_for {my ($class,$field)=@_;return$field}sub mutator_name_for {my ($class,$field)=@_;return$field}sub set {my($self,$key)=splice(@_,0,2);if(@_==1){$self->{$key}=$_[0]}elsif(@_ > 1){$self->{$key}=[@_]}else {$self->_croak("Wrong number of arguments received")}}sub get {my$self=shift;if(@_==1){return$self->{$_[0]}}elsif(@_ > 1){return @{$self}{@_}}else {$self->_croak("Wrong number of arguments received")}}sub make_accessor {my ($class,$field)=@_;return sub {my$self=shift;if(@_){return$self->set($field,@_)}else {return$self->get($field)}}}sub make_ro_accessor {my($class,$field)=@_;return sub {my$self=shift;if (@_){my$caller=caller;$self->_croak("'$caller' cannot alter the value of '$field' on objects of class '$class'")}else {return$self->get($field)}}}sub make_wo_accessor {my($class,$field)=@_;return sub {my$self=shift;unless (@_){my$caller=caller;$self->_croak("'$caller' cannot access the value of '$field' on objects of class '$class'")}else {return$self->set($field,@_)}}}use Carp ();sub _carp {my ($self,$msg)=@_;Carp::carp($msg || $self);return}sub _croak {my ($self,$msg)=@_;Carp::croak($msg || $self);return}1;
CLASS_ACCESSOR
$fatpacked{"Class/Accessor/Fast.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CLASS_ACCESSOR_FAST';
package Class::Accessor::Fast;use base 'Class::Accessor';use strict;$Class::Accessor::Fast::VERSION='0.34';sub make_accessor {my($class,$field)=@_;return sub {return $_[0]->{$field}if scalar(@_)==1;return $_[0]->{$field}=scalar(@_)==2 ? $_[1]: [@_[1..$#_]]}}sub make_ro_accessor {my($class,$field)=@_;return sub {return $_[0]->{$field}if @_==1;my$caller=caller;$_[0]->_croak("'$caller' cannot alter the value of '$field' on objects of class '$class'")}}sub make_wo_accessor {my($class,$field)=@_;return sub {if (@_==1){my$caller=caller;$_[0]->_croak("'$caller' cannot access the value of '$field' on objects of class '$class'")}else {return $_[0]->{$field}=$_[1]if @_==2;return (shift)->{$field}=\@_}}}1;
CLASS_ACCESSOR_FAST
$fatpacked{"Class/Accessor/Faster.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CLASS_ACCESSOR_FASTER';
package Class::Accessor::Faster;use base 'Class::Accessor';use strict;$Class::Accessor::Faster::VERSION='0.34';my%slot;sub _slot {my($class,$field)=@_;my$n=$slot{$class}->{$field};return$n if defined$n;$n=keys %{$slot{$class}};$slot{$class}->{$field}=$n;return$n}sub new {my($proto,$fields)=@_;my($class)=ref$proto || $proto;my$self=bless [],$class;$fields={}unless defined$fields;for my$k (keys %$fields){my$n=$class->_slot($k);$self->[$n]=$fields->{$k}}return$self}sub make_accessor {my($class,$field)=@_;my$n=$class->_slot($field);return sub {return $_[0]->[$n]if scalar(@_)==1;return $_[0]->[$n]=scalar(@_)==2 ? $_[1]: [@_[1..$#_]]}}sub make_ro_accessor {my($class,$field)=@_;my$n=$class->_slot($field);return sub {return $_[0]->[$n]if @_==1;my$caller=caller;$_[0]->_croak("'$caller' cannot alter the value of '$field' on objects of class '$class'")}}sub make_wo_accessor {my($class,$field)=@_;my$n=$class->_slot($field);return sub {if (@_==1){my$caller=caller;$_[0]->_croak("'$caller' cannot access the value of '$field' on objects of class '$class'")}else {return $_[0]->[$n]=$_[1]if @_==2;return (shift)->[$n]=\@_}}}1;
CLASS_ACCESSOR_FASTER
$fatpacked{"Config/Tiny.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'CONFIG_TINY';
package Config::Tiny;use strict;our$VERSION='2.23';BEGIN {require 5.008001;$Config::Tiny::errstr=''}sub new {return bless {},shift}sub read {my($class)=ref $_[0]? ref shift : shift;my($file,$encoding)=@_;return$class -> _error('No file name provided')if (!defined$file || ($file eq ''));$encoding=$encoding ? "<:$encoding" : '<';local $/=undef;open(CFG,$encoding,$file)or return$class -> _error("Failed to open file '$file' for reading: $!");my$contents=<CFG>;close(CFG);return$class -> _error("Reading from '$file' returned undef")if (!defined$contents);return$class -> read_string($contents)}sub read_string {my($class)=ref $_[0]? ref shift : shift;my($self)=bless {},$class;return undef unless defined $_[0];my$ns='_';my$counter=0;for (split /(?:\015{1,2}\012|\015|\012)/,shift){$counter++;next if /^\s*(?:\#|\;|$)/;s/\s\;\s.+$//g;if (/^\s*\[\s*(.+?)\s*\]\s*$/){$self->{$ns=$1}||= {};next}if (/^\s*([^=]+?)\s*=\s*(.*?)\s*$/){$self->{$ns}->{$1}=$2;next}return$self -> _error("Syntax error at line $counter: '$_'")}return$self}sub write {my($self)=shift;my($file,$encoding)=@_;return$self -> _error('No file name provided')if (!defined$file or ($file eq ''));$encoding=$encoding ? ">:$encoding" : '>';my($string)=$self->write_string;return undef unless defined$string;open(CFG,$encoding,$file)or return$self->_error("Failed to open file '$file' for writing: $!");print CFG$string;close CFG;return 1}sub write_string {my($self)=shift;my($contents)='';for my$section (sort {(($b eq '_')<=> ($a eq '_'))|| ($a cmp $b)}keys %$self){return$self->_error("Illegal whitespace in section name '$section'")if$section =~ /(?:^\s|\n|\s$)/s;my$block=$self->{$section};$contents .= "\n" if length$contents;$contents .= "[$section]\n" unless$section eq '_';for my$property (sort keys %$block){return$self->_error("Illegal newlines in property '$section.$property'")if$block->{$property}=~ /(?:\012|\015)/s;$contents .= "$property=$block->{$property}\n"}}return$contents}sub errstr {$Config::Tiny::errstr}sub _error {$Config::Tiny::errstr=$_[1];undef}1;
CONFIG_TINY
$fatpacked{"Devel/InnerPackage.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'DEVEL_INNERPACKAGE';
package Devel::InnerPackage;use strict;use Exporter 5.57 'import';use vars qw($VERSION @EXPORT_OK);use if $] > 5.017,'deprecate';$VERSION='0.4';@EXPORT_OK=qw(list_packages);sub list_packages {my$pack=shift;$pack .= "::" unless$pack =~ m!::$!;no strict 'refs';my@packs;my@stuff=grep!/^(main|)::$/,keys %{$pack};for my$cand (grep /::$/,@stuff){$cand =~ s!::$!!;my@children=list_packages($pack.$cand);push@packs,"$pack$cand" unless$cand =~ /^::/ || !__PACKAGE__->_loaded($pack.$cand);push@packs,@children}return grep {$_ !~ /::(::ISA::CACHE|SUPER)/}@packs}sub _loaded {my ($class,$name)=@_;no strict 'refs';return 1 if defined ${"${name}::VERSION"};return 1 if @{"${name}::ISA"};for (keys %{"${name}::"}){next if substr($_,-2,2)eq '::';return 1 if defined &{"${name}::$_"}}my$filename=join('/',split /(?:'|::)/,$name).'.pm';return 1 if defined$INC{$filename};''}1;
DEVEL_INNERPACKAGE
$fatpacked{"Math/Calc/Units.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS';
package Math::Calc::Units;use Math::Calc::Units::Compute qw(compute);use Math::Calc::Units::Rank qw(render render_unit choose_juicy_ones);use Math::Calc::Units::Convert;use base 'Exporter';use vars qw($VERSION @EXPORT_OK);BEGIN {$VERSION='1.07';@EXPORT_OK=qw(calc readable convert equal exact)}use strict;sub calc ($;$) {my ($expr,$exact)=@_;my$v=compute($expr);return$exact ? ($v->[0],render_unit($v->[1])): render($v)}sub readable {my$expr=shift;my%options;if (@_==1){$options{verbose}=shift}else {%options=@_}my$v=compute($expr);return map {render($_,\%options)}choose_juicy_ones($v,\%options)}sub convert ($$;$) {my ($expr,$units,$exact)=@_;my$v=compute($expr);my$u=compute("# $units");my$c=Math::Calc::Units::Convert::convert($v,$u->[1]);return$exact ? ($c->[0],render_unit($c->[1])): render($c)}use constant EPSILON=>1e-12;sub equal {my ($u,$v)=@_;$u=compute($u);$v=compute($v);$v=Math::Calc::Units::Convert::convert($v,$u->[1]);$u=$u->[0];$v=$v->[0];return 1 if ($u==0)&& abs($v)< EPSILON;return abs(($u-$v)/$u)< EPSILON}if (!(caller)){my$verbose;my%options;if ($ARGV[0]eq '-v'){shift;$options{verbose}=1}if ($ARGV[0]eq '-a'){shift;$options{abbreviate}=1}print "$_\n" foreach readable($ARGV[0],%options)}1;
MATH_CALC_UNITS
$fatpacked{"Math/Calc/Units/Compute.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_COMPUTE';
package Math::Calc::Units::Compute;use base 'Exporter';use vars qw(@EXPORT_OK);@EXPORT_OK=qw(compute plus minus mult divide power unit_mult unit_divide unit_power construct);use strict;use Math::Calc::Units::Convert qw(reduce);use Math::Calc::Units::Rank qw(render_unit);use Math::Calc::Units::Convert::Base;require Math::Calc::Units::Grammar;sub equivalent {my ($u,$v)=@_;return Math::Calc::Units::Convert::Base->same($u,$v)}sub is_unit {my ($x,$unit)=@_;return equivalent($x,{$unit=>1 })}sub plus {my ($u,$v)=@_;$u=reduce($u);$v=reduce($v);if (equivalent($u->[1],$v->[1])){return [$u->[0]+ $v->[0],$u->[1]]}elsif (is_unit($u->[1],'timestamp')&& is_unit($v->[1],'sec')){return [$u->[0]+ $v->[0],$u->[1]]}elsif (is_unit($u->[1],'sec')&& is_unit($v->[1],'timestamp')){return [$u->[0]+ $v->[0],$v->[1]]}die "Unable to add incompatible units `".render_unit($u->[1])."' and `".render_unit($v->[1])."'"}sub minus {my ($u,$v)=@_;$u=reduce($u);$v=reduce($v);if (is_unit($u->[1],'timestamp')&& is_unit($v->[1],'timestamp')){return [$u->[0]- $v->[0],{sec=>1 }]}elsif (equivalent($u->[1],$v->[1])){return [$u->[0]- $v->[0],$u->[1]]}elsif (is_unit($u->[1],'timestamp')&& is_unit($v->[1],'sec')){return [$u->[0]- $v->[0],$u->[1]]}die "Unable to subtract incompatible units `".render_unit($u->[1])."' and `".render_unit($v->[1])."'"}sub mult {my ($u,$v)=@_;return [$u->[0]* $v->[0],unit_mult($u->[1],$v->[1])]}sub divide {my ($u,$v)=@_;return [$u->[0]/ $v->[0],unit_divide($u->[1],$v->[1])]}sub power {my ($u,$v)=@_;die "Can only raise to unit-less powers" if keys %{$v->[1]};$u=reduce($u);if (keys %{$u->[1]}!=0){my$power=$v->[0];die "Can only raise a value with units to an integral power" if abs($power - int($power))> 1e-20;return [$u->[0]** $power,unit_power($u->[1],$power)]}return [$u->[0]** $v->[0],{}]}sub unit_mult {my ($u,$v,$mult)=@_;$mult ||= 1;while (my ($unit,$vp)=each %$v){$u->{$unit}+= $vp * $mult;delete$u->{$unit}if$u->{$unit}==0}return$u}sub unit_divide {my ($u,$v)=@_;return unit_mult($u,$v,-1)}sub unit_power {my ($u,$power)=@_;return {}if$power==0;$u->{$_}*= $power foreach (keys %$u);return$u}sub construct {my$s=shift;my ($constructor,$args)=$s =~ /^(\w+)\((.*)\)/;return Math::Calc::Units::Convert::construct($constructor,$args)}package Math::Calc::Units::Compute;sub tokenize {my$data=shift;my@tokens=$data =~ m{\s*
(
\w+\([^\(\)]*\) # constructed (eg date(2001...))
|[\d.]+ # Numbers
|\w+ # Words
|\*\* # Exponentiation (**)
|[-+*/()@] # Operators
)}xg;my@types=map {/\w\(/ ? 'CONSTRUCT' :(/\d/ ? 'NUMBER' :(/\w/ ? 'WORD' :($_)))}@tokens;return \@tokens,\@types}sub compute {my$expr=shift;my$canonicalize=$expr !~ /^\#/;my ($vals,$types)=tokenize($expr);my$lexer=sub {return shift(@$types),shift(@$vals)if (@$types);return ('',undef)};my$parser=new Math::Calc::Units::Grammar;my$v=$parser->YYParse(yylex=>$lexer,yyerror=>sub {my$parser=shift;die "Error: expected ".join(" ",$parser->YYExpect)." got `".$parser->YYCurtok."', rest=".join(" ",@$types)."\nfrom ".join(" ",@$vals)."\n"},yydebug=>0);return$canonicalize ? reduce($v): $v};1;
MATH_CALC_UNITS_COMPUTE
$fatpacked{"Math/Calc/Units/Convert.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT';
package Math::Calc::Units::Convert;use base 'Exporter';use strict;use vars qw(@EXPORT_OK);BEGIN {@EXPORT_OK=qw(convert reduce canonical find_top construct)};use Math::Calc::Units::Convert::Multi qw(to_canonical);sub convert {my ($from,$unit)=@_;my$to=[1,$unit ];my$canon_from=canonical($from);my$canon_to=canonical($to);die "conversion between incompatible units" if not same_units($canon_from->[1],$canon_to->[1]);return [$canon_from->[0]/ $canon_to->[0],$unit ]}sub same_units {my ($u1,$u2)=@_;return if keys %$u1!=keys %$u2;while (my ($bu1,$bp1)=each %$u1){return if!exists$u2->{$bu1};return if$bp1!=$u2->{$bu1}}return 1}sub canonical {my ($v)=@_;my$c=to_canonical($v->[1]);my$w=[$v->[0]* $c->[0],$c->[1]];return$w}sub reduce {my ($v)=@_;return canonical($v,'reduce, please')}sub construct {my ($constructor,$args)=@_;return Math::Calc::Units::Convert::Multi::construct($constructor,$args)}1;
MATH_CALC_UNITS_CONVERT
$fatpacked{"Math/Calc/Units/Convert/Base.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_BASE';
package Math::Calc::Units::Convert::Base;use strict;sub major_pref {return 0}sub major_variants {my ($self,$unit)=@_;return$unit}sub singular {my$self=shift;local $_=shift;return $_ unless /s$/;return $1 if /^(.*[^e])s$/;return $1 if /^(.*(ch|sh))es$/;return $1 if /^(.*[aeiou][^aeiou]e)s$/;chop;return $_}sub unit_map {return {}}sub variants {my ($self,$base)=@_;my$map=$self->unit_map();return ($base,keys %$map)}sub same {my ($self,$u,$v)=@_;return 0 if keys %$u!=keys %$v;while (my ($name,$power)=each %$u){return 0 if!exists$v->{$name};return 0 if$v->{$name}!=$power}return 1}sub simple_convert {my ($self,$from,$to)=@_;return 1 if$from eq $to;my$map=$self->unit_map();my$w=$map->{$from}|| $map->{lc($from)};if (!$w){$from=$self->singular($from);$w=$map->{$from}|| $map->{lc($from)}}return if!$w;if ($w->[1]ne $to){my$submult=$self->simple_convert($w->[1],$to);return if!defined$submult;return$w->[0]* $submult}else {return$w->[0]}}sub to_canonical {my ($self,$unitName)=@_;my$canon=$self->canonical_unit();if ($canon){my$mult=$self->simple_convert($unitName,$canon);return if!defined$mult;return ($mult,$canon)}else {return (1,$self->singular($unitName))}}sub canonical_unit {return}sub abbreviated_canonical_unit {my ($self)=@_;return$self->canonical_unit}my$THRESHOLD=0.01;sub spread {my ($self,$mag,$base,$start,$units)=@_;die if$mag < 0;return [0,$base ]if$mag==0;my$orig=$mag;my@desc;my$started=0;for my$unit (@$units){$started=1 if$unit eq $start;next unless$started;last if ($mag / $orig)< $THRESHOLD;my$mult=$self->simple_convert($unit,$base);my$n=int($mag / $mult);next if$n==0;$mag -= $n * $mult;push@desc,[$n,$unit ]}return@desc}sub range_score {my ($self,$val,$unitName)=@_;my$ranges=$self->get_ranges();my$range=$ranges->{$unitName}|| $ranges->{default};if ($val >= $range->[0]){if (!defined$range->[1]|| ($val <= $range->[1])){return 1}}$val=_sillylog($val);my$r0=_sillylog($range->[0]);my$r1;if (defined$range->[1]){$r1=_sillylog($range->[1])}else {$r1=4}my$width=$r1 - $r0;my$mean=($r0 + $r1)/ 2;my$stddev=$width / 2;my$n=($val - $mean)/ $stddev;our$mulconst;$mulconst ||= 0.999 * exp(1/8);return 0.001 + $mulconst * exp(-$n**2/2)}sub _sillylog {my$x=shift;return log($x)if$x;return log(1e-50)}sub pref_score {my ($self,$unitName)=@_;my$prefs=$self->get_prefs();my$specific=$prefs->{$unitName};return defined($specific)? $specific : $prefs->{default}}sub get_prefs {return {default=>0.1 }}sub get_ranges {return {default=>[1,undef ]}}sub render_unit {my ($self,$name,$power,$options)=@_;if ($power==1){return$name}else {return "$name**$power"}}sub render {my ($self,$val,$name,$power,$options)=@_;return sprintf("%.5g ",$val).$self->render_unit($name,$power,$options)}sub construct {return}1;
MATH_CALC_UNITS_CONVERT_BASE
$fatpacked{"Math/Calc/Units/Convert/Base2Metric.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_BASE2METRIC';
package Math::Calc::Units::Convert::Base2Metric;use base 'Math::Calc::Units::Convert::Metric';use strict;use vars qw(%metric_base2 %abbrev $metric_prefix_test %pref);%metric_base2=(kilo=>2**10,mega=>2**20,giga=>2**30,tera=>2**40,peta=>2**50,exa=>2**60,);%abbrev=(k=>'kilo',m=>'mega',g=>'giga',t=>'tera',p=>'peta',e=>'exa',);%pref=(unit=>1.0,kilo=>0.8,mega=>0.8,giga=>0.8,tera=>0.7,peta=>0.6,exa=>0.3,);sub get_metric {my ($self,$what)=@_;return$metric_base2{$what}}sub get_abbrev {my ($self,$what)=@_;return$abbrev{$what}|| $abbrev{lc($what)}}$metric_prefix_test=qr/^(${\join("|",keys %metric_base2)})/i;sub get_prefix {my ($self,$what)=@_;if ($what =~ $metric_prefix_test){return $1}else {return}}sub prefix_pref {my ($self,$prefix)=@_;return$pref{lc($prefix)}|| $pref{unit}}sub get_prefixes {return keys%metric_base2}sub expand {my ($self,$char)=@_;return$self->get_abbrev($char)}1;
MATH_CALC_UNITS_CONVERT_BASE2METRIC
$fatpacked{"Math/Calc/Units/Convert/Byte.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_BYTE';
package Math::Calc::Units::Convert::Byte;use base 'Math::Calc::Units::Convert::Base2Metric';use strict;my%units=(bit=>[1/8,'byte' ]);my%pref=(bit=>0.1,default=>1);my%ranges=(default=>[1,999 ]);my%total_unit_map;sub major_pref {return 1}sub major_variants {my ($self)=@_;return$self->variants('byte')}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units)}return \%total_unit_map}sub canonical_unit {return 'byte'}sub abbreviated_canonical_unit {return 'B'}sub simple_convert {my ($self,$from,$to)=@_;return 1 if$from =~ /^b(yte(s?))?$/i;if (my$easy=$self->SUPER::simple_convert($from,$to)){return$easy}if ($from =~ /^(.)b(yte(s?))?$/i){if (my ($prefix)=$self->expand($1)){return$self->simple_convert($prefix ."byte",$to)}}return}1;
MATH_CALC_UNITS_CONVERT_BYTE
$fatpacked{"Math/Calc/Units/Convert/Combo.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_COMBO';
package Math::Calc::Units::Convert::Combo;use base 'Math::Calc::Units::Convert::Base2Metric';use strict;use vars qw(%units %metric_units %prefixable_metric_units %total_unit_map);use vars qw(%ranges %pref);%units=();%metric_units=();%prefixable_metric_units=(bps=>[1,{bit=>1,sec=>-1 }]);%ranges=(default=>[1,999 ]);%pref=(default=>1);sub canonical_unit {return}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units,%metric_units,%prefixable_metric_units)}return \%total_unit_map}sub singular {my ($self,$unit)=@_;return$self->SUPER::singular($unit)unless$unit =~ /bps$/;return$unit}sub demetric {my ($self,$string)=@_;if (my$prefix=$self->get_prefix($string)){my$tail=lc($self->singular(substr($string,length($prefix))));if ($metric_units{$tail}){return ($self->get_metric($prefix),$tail)}}elsif (my$abbrev=$self->get_abbrev_prefix($string)){my$tail=lc($self->singular(substr($string,length($abbrev))));if ($prefixable_metric_units{$tail}){my$prefix=$self->get_abbrev($abbrev);return ($self->get_metric($prefix),$tail)}}return (1,$string)}sub to_canonical {return}sub lookup_compound {my ($self,$unitName)=@_;for (keys%units,keys%metric_units,keys%prefixable_metric_units){if (my$mult=$self->simple_convert($unitName,$_)){my$u=$units{$_}|| $metric_units{$_}|| $prefixable_metric_units{$_};return [$mult * $u->[0],$u->[1]]}}return}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}1;
MATH_CALC_UNITS_CONVERT_COMBO
$fatpacked{"Math/Calc/Units/Convert/Date.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_DATE';
package Math::Calc::Units::Convert::Date;use base 'Math::Calc::Units::Convert::Base';use Time::Local qw(timegm);use strict;use vars qw(%units %pref %ranges %total_unit_map);my$min_nice_time=timegm(0,0,0,1,0,1975-1900);my$max_nice_time=timegm(0,0,0,1,0,2030-1900);%units=();%pref=(default=>1);%ranges=(timestamp=>[$min_nice_time,$max_nice_time ]);sub major_pref {return 2}sub canonical_unit {return 'timestamp'}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units)}return \%total_unit_map}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}use vars qw(@MonthNames);BEGIN {@MonthNames=qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)}sub construct {my ($self,$constructor,$args)=@_;if ($constructor eq 'timestamp'){$args=time if$args eq '';return [$args,{'timestamp'=>1 }]}return unless$constructor eq 'date';$args =~ s/\s+GMT\s+$//;my ($Mon,$d,$y,$h,$m,$s,$tz,$M);$tz='GMT';if ($args =~ /^((?:\w\w\w\s+)?)
(\w\w\w)\s*
(\d+)\s+
(\d+):(\d+)[:.](\d+)\s+
(\w+)?\s*
(\d\d\d\d)$/x){(undef,$Mon,$d,$h,$m,$s,$tz,$y)=($1,$2,$3,$4,$5,$6,$7,$8)}elsif ($args =~ /^(\w\w\w)[\s-]*
(\d+)[,\s-]+
(\d\d\d\d)$/x){($Mon,$d,$y)=($1,$2,$3)}elsif ($args =~ /^(\d\d\d\d)-(\d+)-(\d+)\s+
(\d+):(\d+)[:.](\d+)$/x){($y,$M,$d,$h,$m,$s)=($1,$2,$3,$4,$5,$6);$M--}elsif ($args =~ /^(\d\d\d\d)-(\d+)-(\d+)$/){($y,$M,$d)=($1,$2,$3);$M--}else {die "Unparseable date string '$args'"}$h ||= 0;$m ||= 0;$s ||= 0;if (defined$Mon){$M=0;for (@MonthNames){last if lc($_)eq lc($Mon);$M++}die "Unparseable month '$Mon'" if$M > 11}if (defined($tz)&& $tz ne 'GMT'){warn "Timezones not supported. Assuming GMT.\n"}my$timestamp=timegm($s,$m,$h,$d,$M,$y-1900);die "Date '$args' is out of range" if$timestamp==-1;return [$timestamp,{'timestamp'=>1 }]}sub render {my ($self,$mag,$name,$power)=@_;return "\@$mag" if$power!=1;return "\@$mag" if$mag < $min_nice_time;return "\@$mag" if$mag > $max_nice_time;return gmtime($mag)." (\@$mag)"}1;
MATH_CALC_UNITS_CONVERT_DATE
$fatpacked{"Math/Calc/Units/Convert/Distance.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_DISTANCE';
package Math::Calc::Units::Convert::Distance;use base 'Math::Calc::Units::Convert::Metric';use strict;my%total_unit_map;my%ranges=(default=>[1,999 ]);my%distance_units=(inch=>[2.54,'centimeter' ],foot=>[12,'inch' ],yard=>[3,'foot' ],mile=>[5280,'foot' ],);my%distance_pref=(meter=>1.1,inch=>0.7,foot=>0.9,yard=>0,mile=>1.0,);my%aliases=('feet'=>'foot',);sub canonical_unit {return 'meter'}sub abbreviated_canonical_unit {return 'm'}sub major_pref {return 1}sub major_variants {my ($self)=@_;return$self->variants('meter')}sub get_ranges {return \%ranges}sub get_prefs {return \%distance_pref}sub singular {my ($self,$unit)=@_;$unit=$self->SUPER::singular($unit);return$aliases{$unit}|| $unit}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%distance_units)}return \%total_unit_map}sub simple_convert {my ($self,$from,$to)=@_;return 1 if$from =~ /^m(eter(s?))?$/i;if (my$easy=$self->SUPER::simple_convert($from,$to)){return$easy}if ($from =~ /^(.)m(eter(s?))?$/i){if (my ($prefix)=$self->expand($1)){return$self->simple_convert($prefix ."meter",$to)}}return}sub variants {my ($self,$base)=@_;my$canon=$self->canonical_unit();return ($base,keys %{$self->unit_map()},map {"$_$canon"}$self->get_prefixes())}1;
MATH_CALC_UNITS_CONVERT_DISTANCE
$fatpacked{"Math/Calc/Units/Convert/Metric.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_METRIC';
package Math::Calc::Units::Convert::Metric;use base 'Math::Calc::Units::Convert::Base';use strict;use vars qw(%niceSmallMetric %metric %pref %abbrev %reverse_abbrev $metric_prefix_test);%niceSmallMetric=(milli=>1e-3,micro=>1e-6,nano=>1e-9,pico=>1e-12,femto=>1e-15,);%metric=(kilo=>1e3,mega=>1e6,giga=>1e9,tera=>1e12,peta=>1e15,exa=>1e18,centi=>1e-2,%niceSmallMetric,);%pref=(unit=>1.0,kilo=>0.8,mega=>0.8,giga=>0.8,tera=>0.7,peta=>0.6,exa=>0.3,centi=>0.1,milli=>0.8,micro=>0.8,nano=>0.6,pico=>0.4,femto=>0.3,);%abbrev=(k=>'kilo',M=>'mega',G=>'giga',T=>'tera',P=>'peta',E=>'exa',c=>'centi',m=>'milli',u=>'micro',n=>'nano',p=>'pico',f=>'femto',);%reverse_abbrev=reverse%abbrev;sub pref_score {my ($self,$unitName)=@_;my$prefix=$self->get_prefix($unitName);$unitName=substr($unitName,length($prefix || ""));my$prefix_pref=defined($prefix)? $self->prefix_pref($prefix): 1;return$prefix_pref * $self->SUPER::pref_score($unitName)}sub get_metric {my ($self,$what)=@_;return$metric{$what}}sub get_abbrev {my ($self,$what)=@_;return$abbrev{$what}}$metric_prefix_test=qr/^(${\join("|",keys %metric)})/i;sub get_prefix {my ($self,$what)=@_;if ($what =~ $metric_prefix_test){return $1}else {return}}sub get_prefixes {my ($self,$options)=@_;if ($options->{small}){return grep {$metric{$_}< 1}keys%metric}else {return keys%metric}}sub get_abbrev_prefix {my ($self,$what)=@_;my$prefix=substr($what,0,1);if ($abbrev{$prefix}|| $abbrev{lc($prefix)}){return$prefix}else {return}}sub variants {my ($self,$base)=@_;my@main=$self->SUPER::variants($base);my@variants;for my$u (@main){push@variants,$u,map {"$_$u"}$self->get_prefixes()}return@variants}sub prefix_pref {my ($self,$prefix)=@_;return$pref{lc($prefix)}|| $pref{unit}}sub demetric {my ($self,$string)=@_;if (my$prefix=$self->get_prefix($string)){my$base=substr($string,length($prefix));return ($self->get_metric($prefix),$base)}else {return (1,$string)}}sub expand {my ($self,$char)=@_;my@expansions;my ($exact,$lower);if ($exact=$self->get_abbrev($char)){push@expansions,$exact}elsif (($char ne lc($char))&& ($lower=$self->get_abbrev(lc($char)))){push@expansions,$lower}return@expansions}sub simple_convert {my ($self,$from,$to)=@_;my ($mult_from,$base_from)=$self->demetric($from)or return;my ($mult_to,$base_to)=$self->demetric($to)or return;my$submult=$self->SUPER::simple_convert($base_from,$base_to);return if!defined$submult;return$submult * ($mult_from / $mult_to)}sub metric_abbreviation {my ($self,$prefix)=@_;return$reverse_abbrev{$prefix}|| $prefix}sub render {my ($self,$val,$name,$power,$options)=@_;if ($options->{abbreviate}){my$stem=$self->canonical_unit;if ($name =~ /(\w+)\Q$stem\E$/){my$prefix=$reverse_abbrev{$1};if (defined($prefix)){$name=$prefix .$self->abbreviated_canonical_unit}}}return$self->SUPER::render($val,$name,$power,$options)}1;
MATH_CALC_UNITS_CONVERT_METRIC
$fatpacked{"Math/Calc/Units/Convert/Multi.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_MULTI';
package Math::Calc::Units::Convert::Multi;use base 'Exporter';use vars qw(@EXPORT_OK);BEGIN {@EXPORT_OK=qw(to_canonical simple_convert singular variants major_variants major_pref range_score pref_score get_class construct)};require Math::Calc::Units::Convert::Time;require Math::Calc::Units::Convert::Byte;require Math::Calc::Units::Convert::Date;require Math::Calc::Units::Convert::Distance;require Math::Calc::Units::Convert::Combo;use strict;use vars qw(@UnitClasses);@UnitClasses=qw(Math::Calc::Units::Convert::Time Math::Calc::Units::Convert::Byte Math::Calc::Units::Convert::Date Math::Calc::Units::Convert::Distance Math::Calc::Units::Convert::Combo);sub to_canonical {my ($unit)=@_;my$val=1;my%newUnit;while (my ($unitName,$power)=each %$unit){my ($mult,$canon)=name_to_canonical($unitName);$val *= $mult ** $power;if (ref$canon){my$c=to_canonical($canon);$val *= $c->[0]** $power;while (my ($name,$subPower)=each %{$c->[1]}){if (($newUnit{$name}+= $subPower * $power)==0){delete$newUnit{$name}}}}else {if (($newUnit{$canon}+= $power)==0){delete$newUnit{$canon}}}}return [$val,\%newUnit ]}my%CANON_CACHE;sub name_to_canonical {my$unitName=shift;$CANON_CACHE{$unitName}||= [_name_to_canonical($unitName)];return @{$CANON_CACHE{$unitName}}}sub _name_to_canonical {my ($unitName)=@_;if (my$v=Math::Calc::Units::Convert::Combo->lookup_compound($unitName)){return @$v}for my$uclass (@UnitClasses){if (my ($val,$base)=$uclass->to_canonical($unitName)){return ($val,$base)}}return Math::Calc::Units::Convert::Base->to_canonical($unitName)}sub get_class {my ($unitName)=@_;my (undef,$canon)=name_to_canonical($unitName);for my$uclass (@UnitClasses){my$canon_unit=$uclass->canonical_unit();next if!defined$canon_unit;return$uclass if$canon_unit eq $canon}return 'Math::Calc::Units::Convert::Base'}sub simple_convert {my ($u,$v)=@_;for my$uclass (@UnitClasses){my$c;return$c if$c=$uclass->simple_convert($u,$v)}return}sub singular {my ($unitName)=@_;return get_class($unitName)->singular($unitName)}sub variants {my ($base)=@_;return get_class($base)->variants($base)}sub major_variants {my ($base)=@_;return get_class($base)->major_variants($base)}sub major_pref {my ($base)=@_;return get_class($base)->major_pref($base)}sub range_score {my ($val,$unitName)=@_;die if ref$unitName;return get_class($unitName)->range_score($val,$unitName)}sub pref_score {my ($unitName)=@_;die if ref$unitName;return get_class($unitName)->pref_score($unitName)}sub construct {my ($constructor,$args)=@_;for my$uclass (@UnitClasses){my$c;return$c if$c=$uclass->construct($constructor,$args)}return}1;
MATH_CALC_UNITS_CONVERT_MULTI
$fatpacked{"Math/Calc/Units/Convert/Time.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_CONVERT_TIME';
package Math::Calc::Units::Convert::Time;use base 'Math::Calc::Units::Convert::Metric';use strict;use vars qw(%units %pref %ranges %total_unit_map);%units=(minute=>[60,'sec' ],hour=>[60,'minute' ],day=>[24,'hour' ],week=>[7,'day' ],year=>[365,'day' ],);%pref=(default=>1,hour=>0.8,day=>0.8,week=>0.4,minute=>0.9,year=>0.9,);%ranges=(default=>[1,300 ],millisec=>[1,999 ],sec=>[1,200 ],minute=>[2,100 ],hour=>[1,80 ],day=>[1,500 ],week=>[1,4 ],year=>[1,undef ],);sub major_pref {return 2}sub major_variants {my ($self)=@_;return grep {($_ ne 'default')&& ($_ ne 'week')}keys%ranges}sub variants {my ($self,$base)=@_;return 'sec',(keys%units),map {"${_}sec"}$self->get_prefixes({small=>1 })}sub unit_map {my ($self)=@_;if (keys%total_unit_map==0){%total_unit_map=(%{$self->SUPER::unit_map()},%units)}return \%total_unit_map}sub canonical_unit {return 'sec'}sub abbreviated_canonical_unit {return 's'}sub demetric {my ($self,$string)=@_;if (my$prefix=$self->get_prefix($string)){my$tail=substr($string,length($prefix));if ($tail =~ /^sec(ond)?s?$/){return ($self->get_metric($prefix),"sec")}return}else {return (1,$string)}}sub simple_convert {my ($self,$from,$to)=@_;$from="sec" if$from =~ /^sec(ond)?s?$/i;$from="minute" if$from =~ /^min(ute)?s?$/i;if (my$easy=$self->SUPER::simple_convert($from,$to)){return$easy}if ($from =~ /^(.)s$/){my ($expansion)=$self->expand($1);return$self->simple_convert($expansion ."sec",$to)}return}sub preference {my ($self,$v)=@_;my ($val,$unit)=@$v;my$base=lc(($self->demetric($unit))[1]);my$pref=$pref{$base}|| $pref{default};return$pref * $self->prefix_pref(substr($unit,0,-length($base)))}sub get_ranges {return \%ranges}sub get_prefs {return \%pref}my@BREAKDOWN=qw(year week day hour minute sec ms us ns ps);sub render {my ($self,$val,$name,$power,$options)=@_;my$full_name=$name;if ($options->{abbreviate}){if ($name =~ /(\w+)sec/){my$prefix=$1;my$mabbrev=$self->metric_abbreviation($prefix);$name=$mabbrev ."s" unless$mabbrev eq $prefix}}my$basic=$self->SUPER::render($val,$name,$power,$options);return$basic if$power!=1;$val *= $self->simple_convert($full_name,'sec');my@spread=$self->spread($val,'sec',$name,\@BREAKDOWN);my$spread=join(" ",map {"$_->[0] $_->[1]"}@spread);return "($basic = $spread)" if@spread > 1;return$basic}1;
MATH_CALC_UNITS_CONVERT_TIME
$fatpacked{"Math/Calc/Units/Grammar.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_GRAMMAR';
package Math::Calc::Units::Grammar;use vars qw ( @ISA);use strict;@ISA=qw ( Parse::Yapp::Driver);{package Parse::Yapp::Driver;require 5.004;use strict;use vars qw ( $VERSION $COMPATIBLE $FILENAME);$VERSION='1.04';$COMPATIBLE='0.07';$FILENAME=__FILE__;use Carp;my(%params)=(YYLEX=>'CODE','YYERROR'=>'CODE',YYVERSION=>'',YYRULES=>'ARRAY',YYSTATES=>'ARRAY',YYDEBUG=>'');my(@params)=('LEX','RULES','STATES');sub new {my($class)=shift;my($errst,$nberr,$token,$value,$check,$dotpos);my($self)={ERROR=>\&_Error,ERRST=>\$errst,NBERR=>\$nberr,TOKEN=>\$token,VALUE=>\$value,DOTPOS=>\$dotpos,STACK=>[],DEBUG=>0,CHECK=>\$check };_CheckParams([],\%params,\@_,$self);exists($$self{VERSION})and $$self{VERSION}< $COMPATIBLE and croak "Yapp driver version $VERSION "."incompatible with version $$self{VERSION}:\n"."Please recompile parser module.";ref($class)and $class=ref($class);bless($self,$class)}sub YYParse {my($self)=shift;my($retval);_CheckParams(\@params,\%params,\@_,$self);if($$self{DEBUG}){_DBLoad();$retval=eval '$self->_DBParse()';$@ and die $@}else {$retval=$self->_Parse()}$retval}sub YYData {my($self)=shift;exists($$self{USER})or $$self{USER}={};$$self{USER}}sub YYErrok {my($self)=shift;${$$self{ERRST}}=0;undef}sub YYNberr {my($self)=shift;${$$self{NBERR}}}sub YYRecovering {my($self)=shift;${$$self{ERRST}}!=0}sub YYAbort {my($self)=shift;${$$self{CHECK}}='ABORT';undef}sub YYAccept {my($self)=shift;${$$self{CHECK}}='ACCEPT';undef}sub YYError {my($self)=shift;${$$self{CHECK}}='ERROR';undef}sub YYSemval {my($self)=shift;my($index)=$_[0]- ${$$self{DOTPOS}}- 1;$index < 0 and -$index <= @{$$self{STACK}}and return $$self{STACK}[$index][1];undef}sub YYCurtok {my($self)=shift;@_ and ${$$self{TOKEN}}=$_[0];${$$self{TOKEN}}}sub YYCurval {my($self)=shift;@_ and ${$$self{VALUE}}=$_[0];${$$self{VALUE}}}sub YYExpect {my($self)=shift;keys %{$self->{STATES}[$self->{STACK}[-1][0]]{ACTIONS}}}sub YYLexer {my($self)=shift;$$self{LEX}}sub _CheckParams {my($mandatory,$checklist,$inarray,$outhash)=@_;my($prm,$value);my($prmlst)={};while(($prm,$value)=splice(@$inarray,0,2)){$prm=uc($prm);exists($$checklist{$prm})or croak("Unknow parameter '$prm'");ref($value)eq $$checklist{$prm}or croak("Invalid value for parameter '$prm'");$prm=unpack('@2A*',$prm);$$outhash{$prm}=$value}for (@$mandatory){exists($$outhash{$_})or croak("Missing mandatory parameter '".lc($_)."'")}}sub _Error {print "Parse error.\n"}sub _DBLoad {{no strict 'refs';exists(${__PACKAGE__.'::'}{_DBParse})and return}my($fname)=__FILE__;my(@drv);open(DRV,"<$fname")or die "Report this as a BUG: Cannot open $fname";while(<DRV>){/^\s*sub\s+_Parse\s*{\s*$/ .. /^\s*}\s*#\s*_Parse\s*$/ and do {s/^#DBG>//;push(@drv,$_)}}close(DRV);$drv[0]=~s/_P/_DBP/;eval join('',@drv)}sub _Parse {my($self)=shift;my($rules,$states,$lex,$error)=@$self{'RULES','STATES','LEX','ERROR' };my($errstatus,$nberror,$token,$value,$stack,$check,$dotpos)=@$self{'ERRST','NBERR','TOKEN','VALUE','STACK','CHECK','DOTPOS' };$$errstatus=0;$$nberror=0;($$token,$$value)=(undef,undef);@$stack=([0,undef ]);$$check='';while(1){my($actions,$act,$stateno);$stateno=$$stack[-1][0];$actions=$$states[$stateno];if (exists($$actions{ACTIONS})){defined($$token)or do {($$token,$$value)=&$lex($self)};$act=exists($$actions{ACTIONS}{$$token})? $$actions{ACTIONS}{$$token}: exists($$actions{DEFAULT})? $$actions{DEFAULT}: undef}else {$act=$$actions{DEFAULT}}defined($act)and do {$act > 0 and do {$$errstatus and do {--$$errstatus};push(@$stack,[$act,$$value ]);$$token ne '' and $$token=$$value=undef;next};my($lhs,$len,$code,@sempar,$semval);($lhs,$len,$code)=@{$$rules[-$act]};$act or $self->YYAccept();$$dotpos=$len;unpack('A1',$lhs)eq '@' and do {$lhs =~ /^\@[0-9]+\-([0-9]+)$/ or die "In line rule name '$lhs' ill formed: "."report it as a BUG.\n";$$dotpos=$1};@sempar=$$dotpos ? map {$$_[1]}@$stack[-$$dotpos .. -1 ]: ();$semval=$code ? &$code($self,@sempar): @sempar ? $sempar[0]: undef;splice(@$stack,-$len,$len);$$check eq 'ACCEPT' and do {return($semval)};$$check eq 'ABORT' and do {return(undef)};$$check eq 'ERROR' or do {push(@$stack,[$$states[$$stack[-1][0]]{GOTOS}{$lhs},$semval ]);$$check='';next};$$check=''};$$errstatus or do {$$errstatus=1;&$error($self);$$errstatus or next;++$$nberror};$$errstatus==3 and do {$$token eq '' and do {return(undef)};$$token=$$value=undef};$$errstatus=3;while(@$stack and (not exists($$states[$$stack[-1][0]]{ACTIONS})or not exists($$states[$$stack[-1][0]]{ACTIONS}{error})or $$states[$$stack[-1][0]]{ACTIONS}{error}<= 0)){pop(@$stack)}@$stack or do {return(undef)};push(@$stack,[$$states[$$stack[-1][0]]{ACTIONS}{error},undef ])}croak("Error in driver logic. Please, report it as a BUG")}1}use Math::Calc::Units::Compute qw(plus minus mult divide power construct);sub new {my($class)=shift;ref($class)and $class=ref($class);my($self)=$class->SUPER::new(yyversion=>'1.04',yystates=>[{ACTIONS=>{'NUMBER'=>5,"#"=>2,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'START'=>10,'expr'=>11 }},{DEFAULT=>-12 },{ACTIONS=>{'WORD'=>7 },GOTOS=>{'unit'=>12 }},{DEFAULT=>-16 },{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>13 }},{ACTIONS=>{'WORD'=>7 },DEFAULT=>-13,GOTOS=>{'unit'=>14 }},{ACTIONS=>{'NUMBER'=>15 }},{DEFAULT=>-17 },{DEFAULT=>-9 },{ACTIONS=>{'NUMBER'=>17 }},{ACTIONS=>{''=>18 }},{ACTIONS=>{"*"=>21,"+"=>22,"**"=>20,"-"=>23,'WORD'=>7,"/"=>24 },DEFAULT=>-1,GOTOS=>{'unit'=>19 }},{DEFAULT=>-2 },{ACTIONS=>{"*"=>21,"+"=>22,"**"=>20,"-"=>23,'WORD'=>7,"/"=>24,")"=>25 },GOTOS=>{'unit'=>19 }},{DEFAULT=>-11 },{DEFAULT=>-14 },{DEFAULT=>-18 },{DEFAULT=>-15 },{DEFAULT=>-0 },{DEFAULT=>-10 },{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>26 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>27 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>28 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>29 }},{ACTIONS=>{'NUMBER'=>5,"-"=>6,'WORD'=>7,'CONSTRUCT'=>3,"\@"=>9,"("=>4 },GOTOS=>{'unit'=>1,'value'=>8,'expr'=>30 }},{DEFAULT=>-8 },{DEFAULT=>-7,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,'WORD'=>7 },DEFAULT=>-5,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,"*"=>21,'WORD'=>7,"/"=>24 },DEFAULT=>-3,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,"*"=>21,'WORD'=>7,"/"=>24 },DEFAULT=>-4,GOTOS=>{'unit'=>19 }},{ACTIONS=>{"**"=>20,'WORD'=>7 },DEFAULT=>-6,GOTOS=>{'unit'=>19 }}],yyrules=>[['$start',2,undef ],['START',1,undef ],['START',2,undef ],['expr',3,sub {return plus($_[1],$_[3])}],['expr',3,sub {return minus($_[1],$_[3])}],['expr',3,sub {return mult($_[1],$_[3])}],['expr',3,sub {return divide($_[1],$_[3])}],['expr',3,sub {return power($_[1],$_[3])}],['expr',3,sub {return $_[2]}],['expr',1,sub {return $_[1]}],['expr',2,sub {return mult($_[1],[1,$_[2]])}],['value',2,sub {return [$_[1]=>$_[2]]}],['value',1,sub {return [1=>$_[1]]}],['value',1,sub {return [$_[1]=>{}]}],['value',2,sub {return [-$_[2]=>{}]}],['value',2,sub {return [$_[2]=>{'timestamp'=>1 }]}],['value',1,sub {return construct($_[1])}],['unit',1,sub {return {$_[1]=>1 }}],['unit',2,sub {my$u={};$u->{$_[1]}++;$u->{$_[2]}++;return$u}]],@_);bless($self,$class)}1;
MATH_CALC_UNITS_GRAMMAR
$fatpacked{"Math/Calc/Units/Rank.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MATH_CALC_UNITS_RANK';
package Math::Calc::Units::Rank;use base 'Exporter';use vars qw(@EXPORT_OK);BEGIN {@EXPORT_OK=qw(choose_juicy_ones render render_unit)}use Math::Calc::Units::Convert qw(convert canonical);use Math::Calc::Units::Convert::Multi qw(variants major_variants major_pref pref_score range_score get_class);use strict;sub choose_juicy_ones {my ($v,$options)=@_;my@variants=rank_variants($v,$options);my%variants;for my$variant (@variants){my$id=join(";;",values %{$variant->[0]});$variants{$id}=$variant}my@options;for my$variant (values%variants){my ($map,$score)=@$variant;my%copy;my ($magnitude,$units)=@$v;while (my ($unit,$count)=each %$units){$copy{$map->{$unit}}=$count}push@options,[$score,convert($v,\%copy)]}my@juicy;my$first;my$prev;for (sort {$b->[0]<=> $a->[0]}@options){my ($score,$val)=@$_;last if (defined$prev && ($prev / $score)> 8);last if (defined$first && ($first / $score)> 25);push@juicy,$val;$first=$score unless defined$first;$prev=$score;last if@juicy==5}return@juicy}sub rank_variants {my ($v,$options)=@_;$v=canonical($v);my ($mag,$count)=@$v;my@rangeable=grep {$count->{$_}> 0}keys %$count;if (@rangeable==0){@rangeable=keys %$count}return rank_power_variants($mag,\@rangeable,$count,$options)}sub choose_major {my (@possibilities)=@_;my@majors=map {[major_pref($_),$_ ]}@possibilities;return (sort {$a->[0]<=> $b->[0]}@majors)[-1]->[1]}sub rank_power_variants {my ($mag,$top,$power,$options)=@_;if (keys %$power > 1){my$major=choose_major(keys %$power);my$majorClass=get_class($major);my%powerless=%$power;delete$powerless{$major};my@ranked;for my$variant (major_variants($major,$options)){my$mult=$majorClass->simple_convert($variant,$major);my$cval=$mag / $mult ** $power->{$major};print "\n --- for $variant ---\n" if$options->{verbose};my@r=rank_power_variants($cval,$top,\%powerless,$options);next if@r==0;my$best=$r[0];$best->[0]->{$major}=$variant;$best->[1]=pref_score($variant);push@ranked,$best}return@ranked}if (keys %$power==0){return [{},1 ]}my$unit=(keys %$power)[0];$power=$power->{$unit};my$class=get_class($unit);my (undef,$canon)=$class->to_canonical($unit);my$mult=$class->simple_convert($unit,$canon);$mag *= $mult ** $power;my@choices;my@subtop=grep {$_ ne $canon}@$top;my$add_variant=(@subtop==@$top);for my$variant (variants($canon)){my$mult=$class->simple_convert($variant,$canon);my$minimag=$mag / $mult ** $power;my@vtop=@subtop;push@vtop,$variant if$add_variant;my$score=score($minimag,$variant,\@vtop);printf "($mag $unit) score %.6f:\t $minimag $variant\n",$score if$options->{verbose};push@choices,[$score,$variant ]}@choices=sort {$b->[0]<=> $a->[0]}@choices;return ()if@choices==0;return map {[{$unit=>$_->[1]},$_->[0]]}@choices}sub render_unit {my ($units,$options)=@_;my$str='';while (my ($name,$power)=each %$units){if ($power > 0){$str .= get_class($name)->render_unit($name,$power,$options);$str .= " "}}chop($str);my$botstr='';while (my ($name,$power)=each %$units){if ($power < 0){$botstr .= get_class($name)->render_unit($name,-$power,$options);$botstr .= " "}}chop($botstr);if ($botstr eq ''){return$str}elsif ($botstr =~ /\s/){return "$str / ($botstr)"}else {return "$str / $botstr"}}sub render {my ($v,$options)=@_;my ($mag,$units)=@$v;if (keys %$units==0){my$str=sprintf("%.4g",$mag);if (($mag < 1)&& ($mag >= 0.01)){if ($options->{abbreviate}){$str .= sprintf(" = %.4g percent",100 * $mag)}else {$str .= sprintf(" = %.4g%%",100 * $mag)}}return$str}my@top;my@bottom;while (my ($name,$power)=each %$units){if ($power > 0){push@top,$name}else {push@bottom,$name}}my$str;if (@top==1){my ($name)=@top;$str=get_class($name)->render($mag,$name,$units->{$name},$options);$str .= " "}else {$str=sprintf("%.4g ",$mag);for my$name (@top){$str .= get_class($name)->render_unit($name,$units->{$name},$options);$str .= " "}}if (@bottom > 0){my$botstr;for my$name (@bottom){$botstr .= get_class($name)->render_unit($name,-$units->{$name},$options);$botstr .= " "}chop($botstr);if (@bottom > 1){$str .= "/ ($botstr) "}else {$str .= "/ $botstr "}}chop($str);return$str}sub max_range_score {my ($mag,$units)=@_;my$score=0;for my$name (@$units){my$uscore=range_score($mag,$name);$score=$uscore if$score < $uscore}return$score}sub score {my ($mag,$unit,$top)=@_;my@rangeable=@$top ? @$top : ($unit);my$pref=pref_score($unit);my$range_score=max_range_score($mag,\@rangeable);return$pref * $range_score}1;
MATH_CALC_UNITS_RANK
$fatpacked{"Module/Implementation.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_IMPLEMENTATION';
package Module::Implementation;$Module::Implementation::VERSION='0.09';use strict;use warnings;use Module::Runtime 0.012 qw(require_module);use Try::Tiny;unless (exists$Module::Implementation::{VERSION}&& ${$Module::Implementation::{VERSION}}){$Module::Implementation::{VERSION}=\42}my%Implementation;sub build_loader_sub {my$caller=caller();return _build_loader($caller,@_)}sub _build_loader {my$package=shift;my%args=@_;my@implementations=@{$args{implementations}};my@symbols=@{$args{symbols}|| []};my$implementation;my$env_var=uc$package;$env_var =~ s/::/_/g;$env_var .= '_IMPLEMENTATION';return sub {my ($implementation,$loaded)=_load_implementation($package,$ENV{$env_var},\@implementations,);$Implementation{$package}=$implementation;_copy_symbols($loaded,$package,\@symbols);return$loaded}}sub implementation_for {my$package=shift;return$Implementation{$package}}sub _load_implementation {my$package=shift;my$env_value=shift;my$implementations=shift;if ($env_value){die "$env_value is not a valid implementation for $package" unless grep {$_ eq $env_value}@{$implementations};my$requested="${package}::$env_value";($requested)=$requested =~ /^(.+)$/;try {require_module($requested)}catch {require Carp;Carp::croak("Could not load $requested: $_")};return ($env_value,$requested)}else {my$err;for my$possible (@{$implementations}){my$try="${package}::$possible";my$ok;try {require_module($try);$ok=1}catch {$err .= $_ if defined $_};return ($possible,$try)if$ok}require Carp;if (defined$err && length$err){Carp::croak("Could not find a suitable $package implementation: $err")}else {Carp::croak('Module::Runtime failed to load a module but did not throw a real error. This should never happen. Something is very broken')}}}sub _copy_symbols {my$from_package=shift;my$to_package=shift;my$symbols=shift;for my$sym (@{$symbols}){my$type=$sym =~ s/^([\$\@\%\&\*])// ? $1 : '&';my$from="${from_package}::$sym";my$to="${to_package}::$sym";{no strict 'refs';no warnings 'once';*{$to}=$type eq '&' ? \&{$from}: $type eq '$' ? \${$from}: $type eq '@' ? \@{$from}: $type eq '%' ? \%{$from}: $type eq '*' ? *{$from}: die "Can't copy symbol from $from_package to $to_package: $type$sym"}}}1;
MODULE_IMPLEMENTATION
$fatpacked{"Module/Pluggable.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_PLUGGABLE';
package Module::Pluggable;use strict;use vars qw($VERSION $FORCE_SEARCH_ALL_PATHS);use Module::Pluggable::Object;use if $] > 5.017,'deprecate';$VERSION='5.2';$FORCE_SEARCH_ALL_PATHS=0;sub import {my$class=shift;my%opts=@_;my ($pkg,$file)=caller;my$sub=$opts{'sub_name'}|| 'plugins';my ($package)=$opts{'package'}|| $pkg;$opts{filename}=$file;$opts{package}=$package;$opts{force_search_all_paths}=$FORCE_SEARCH_ALL_PATHS unless exists$opts{force_search_all_paths};my$finder=Module::Pluggable::Object->new(%opts);my$subroutine=sub {my$self=shift;return$finder->plugins(@_)};my$searchsub=sub {my$self=shift;my ($action,@paths)=@_;$finder->{'search_path'}=["${package}::Plugin"]if ($action eq 'add' and not $finder->{'search_path'});push @{$finder->{'search_path'}},@paths if ($action eq 'add');$finder->{'search_path'}=\@paths if ($action eq 'new');return$finder->{'search_path'}};my$onlysub=sub {my ($self,$only)=@_;if (defined$only){$finder->{'only'}=$only};return$finder->{'only'}};my$exceptsub=sub {my ($self,$except)=@_;if (defined$except){$finder->{'except'}=$except};return$finder->{'except'}};no strict 'refs';no warnings qw(redefine prototype);*{"$package\::$sub"}=$subroutine;*{"$package\::search_path"}=$searchsub;*{"$package\::only"}=$onlysub;*{"$package\::except"}=$exceptsub}1;
MODULE_PLUGGABLE
$fatpacked{"Module/Pluggable/Object.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_PLUGGABLE_OBJECT';
package Module::Pluggable::Object;use strict;use File::Find ();use File::Basename;use File::Spec::Functions qw(splitdir catdir curdir catfile abs2rel);use Carp qw(croak carp confess);use Devel::InnerPackage;use vars qw($VERSION $MR);use if $] > 5.017,'deprecate';$VERSION='5.2';BEGIN {eval {require Module::Runtime};unless ($@){Module::Runtime->import('require_module')}else {*require_module=sub {my$module=shift;my$path=$module .".pm";$path =~ s{::}{/}g;require$path}}}sub new {my$class=shift;my%opts=@_;return bless \%opts,$class}sub plugins {my$self=shift;my@args=@_;$self->{'require'}=1 if$self->{'inner'};my$filename=$self->{'filename'};my$pkg=$self->{'package'};$self->_setup_exceptions;for (qw(search_path search_dirs)){$self->{$_}=[$self->{$_}]if exists$self->{$_}&&!ref($self->{$_})}$self->{'search_path'}||= ["${pkg}::Plugin"];$self->{'on_require_error'}||= sub {my ($plugin,$err)=@_;carp "Couldn't require $plugin : $err";return 0};$self->{'on_instantiate_error'}||= sub {my ($plugin,$err)=@_;carp "Couldn't instantiate $plugin: $err";return 0};$self->{'follow_symlinks'}=1 unless exists$self->{'follow_symlinks'};my@SEARCHDIR=exists$INC{"blib.pm"}&& defined$filename && $filename =~ m!(^|/)blib/! &&!$self->{'force_search_all_paths'}? grep {/blib/}@INC : @INC;unshift@SEARCHDIR,@{$self->{'search_dirs'}}if defined$self->{'search_dirs'};my@tmp=@INC;unshift@tmp,@{$self->{'search_dirs'}|| []};local@INC=@tmp if defined$self->{'search_dirs'};my@plugins=$self->search_directories(@SEARCHDIR);push(@plugins,$self->handle_inc_hooks($_,@SEARCHDIR))for @{$self->{'search_path'}};push(@plugins,$self->handle_innerpackages($_))for @{$self->{'search_path'}};return ()unless@plugins;my%plugins;for(@plugins){next unless$self->_is_legit($_);$plugins{$_}=1}if (defined$self->{'instantiate'}){my$method=$self->{'instantiate'};my@objs=();for my$package (sort keys%plugins){next unless$package->can($method);my$obj=eval {$package->$method(@_)};$self->{'on_instantiate_error'}->($package,$@)if $@;push@objs,$obj if$obj}return@objs}else {my@objs=sort keys%plugins;return@objs}}sub _setup_exceptions {my$self=shift;my%only;my%except;my$only;my$except;if (defined$self->{'only'}){if (ref($self->{'only'})eq 'ARRAY'){%only=map {$_=>1}@{$self->{'only'}}}elsif (ref($self->{'only'})eq 'Regexp'){$only=$self->{'only'}}elsif (ref($self->{'only'})eq ''){$only{$self->{'only'}}=1}}if (defined$self->{'except'}){if (ref($self->{'except'})eq 'ARRAY'){%except=map {$_=>1}@{$self->{'except'}}}elsif (ref($self->{'except'})eq 'Regexp'){$except=$self->{'except'}}elsif (ref($self->{'except'})eq ''){$except{$self->{'except'}}=1}}$self->{_exceptions}->{only_hash}=\%only;$self->{_exceptions}->{only}=$only;$self->{_exceptions}->{except_hash}=\%except;$self->{_exceptions}->{except}=$except}sub _is_legit {my$self=shift;my$plugin=shift;my%only=%{$self->{_exceptions}->{only_hash}||{}};my%except=%{$self->{_exceptions}->{except_hash}||{}};my$only=$self->{_exceptions}->{only};my$except=$self->{_exceptions}->{except};my$depth=()=split '::',$plugin,-1;return 0 if (keys%only &&!$only{$plugin});return 0 unless (!defined$only || $plugin =~ m!$only!);return 0 if (keys%except && $except{$plugin});return 0 if (defined$except && $plugin =~ m!$except!);return 0 if defined$self->{max_depth}&& $depth>$self->{max_depth};return 0 if defined$self->{min_depth}&& $depth<$self->{min_depth};return 1}sub search_directories {my$self=shift;my@SEARCHDIR=@_;my@plugins;for my$dir (@SEARCHDIR){push@plugins,$self->search_paths($dir)}return@plugins}sub search_paths {my$self=shift;my$dir=shift;my@plugins;my$file_regex=$self->{'file_regex'}|| qr/\.pm$/;for my$searchpath (@{$self->{'search_path'}}){my$sp=catdir($dir,(split /::/,$searchpath));next unless (-e $sp && -d _);my@files=$self->find_files($sp);for my$file (@files){next unless ($file)=($file =~ /(.*$file_regex)$/);my ($name,$directory,$suffix)=fileparse($file,$file_regex);next if (!$self->{include_editor_junk}&& $self->_is_editor_junk($name));$directory=abs2rel($directory,$sp);my@pkg_dirs=();if ($name eq lc($name)|| $name eq uc($name)){my$pkg_file=catfile($sp,$directory,"$name$suffix");open PKGFILE,"<$pkg_file" or die "search_paths: Can't open $pkg_file: $!";my$in_pod=0;while (my$line=<PKGFILE>){$in_pod=1 if$line =~ m/^=\w/;$in_pod=0 if$line =~ /^=cut/;next if ($in_pod || $line =~ /^=cut/);next if$line =~ /^\s*#/;if ($line =~ m/^\s*package\s+(.*::)?($name)\s*;/i){@pkg_dirs=split /::/,$1 if defined $1;;$name=$2;last}}close PKGFILE}$directory =~ s/^[a-z]://i if($^O =~ /MSWin32|dos/);my@dirs=();if ($directory){($directory)=($directory =~ /(.*)/);@dirs=grep(length($_),splitdir($directory))unless$directory eq curdir();for my$d (reverse@dirs){my$pkg_dir=pop@pkg_dirs;last unless defined$pkg_dir;$d =~ s/\Q$pkg_dir\E/$pkg_dir/i}}else {$directory=""}my$plugin=join '::',$searchpath,@dirs,$name;next unless$plugin =~ m!(?:[a-z\d]+)[a-z\d]*!i;$self->handle_finding_plugin($plugin,\@plugins)}push@plugins,$self->handle_innerpackages($searchpath)}return@plugins}sub _is_editor_junk {my$self=shift;my$name=shift;return 1 if$name =~ /~$/;return 1 if$name =~ /^\.#/;return 1 if$name =~ /\.sw[po]$/;return 0}sub handle_finding_plugin {my$self=shift;my$plugin=shift;my$plugins=shift;my$no_req=shift || 0;return unless$self->_is_legit($plugin);unless (defined$self->{'instantiate'}|| $self->{'require'}){push @$plugins,$plugin;return}$self->{before_require}->($plugin)|| return if defined$self->{before_require};unless ($no_req){my$tmp=$@;my$res=eval {require_module($plugin)};my$err=$@;$@=$tmp;if ($err){if (defined$self->{on_require_error}){$self->{on_require_error}->($plugin,$err)|| return}else {return}}}$self->{after_require}->($plugin)|| return if defined$self->{after_require};push @$plugins,$plugin}sub find_files {my$self=shift;my$search_path=shift;my$file_regex=$self->{'file_regex'}|| qr/\.pm$/;my@files=();{local $_;File::Find::find({no_chdir=>1,follow=>$self->{'follow_symlinks'},wanted=>sub {return unless$File::Find::name =~ /$file_regex/;(my$path=$File::Find::name)=~ s#^\\./##;push@files,$path}},$search_path)}return@files}sub handle_inc_hooks {my$self=shift;my$path=shift;my@SEARCHDIR=@_;my@plugins;for my$dir (@SEARCHDIR){next unless ref$dir && eval {$dir->can('files')};for my$plugin ($dir->files){$plugin =~ s/\.pm$//;$plugin =~ s{/}{::}g;next unless$plugin =~ m!^${path}::!;$self->handle_finding_plugin($plugin,\@plugins)}}return@plugins}sub handle_innerpackages {my$self=shift;return ()if (exists$self->{inner}&&!$self->{inner});my$path=shift;my@plugins;for my$plugin (Devel::InnerPackage::list_packages($path)){$self->handle_finding_plugin($plugin,\@plugins,1)}return@plugins}1;
MODULE_PLUGGABLE_OBJECT
$fatpacked{"Module/Runtime.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MODULE_RUNTIME';
package Module::Runtime;BEGIN {require 5.006}BEGIN {${^WARNING_BITS}=""}our$VERSION="0.014";our@EXPORT_OK=qw($module_name_rx is_module_name is_valid_module_name check_module_name module_notional_filename require_module use_module use_package_optimistically $top_module_spec_rx $sub_module_spec_rx is_module_spec is_valid_module_spec check_module_spec compose_module_name);my%export_ok=map {($_=>undef)}@EXPORT_OK;sub import {my$me=shift;my$callpkg=caller(0);my$errs="";for(@_){if(exists$export_ok{$_}){if(/\A\$(.*)\z/s){*{$callpkg."::".$1}=\$$1}else {*{$callpkg."::".$_}=\&$_}}else {$errs .= "\"$_\" is not exported by the $me module\n"}}if($errs ne ""){die "${errs}Can't continue after import errors "."at @{[(caller(0))[1]]} line @{[(caller(0))[2]]}.\n"}}sub _is_string($) {my($arg)=@_;return defined($arg)&& ref(\$arg)eq "SCALAR"}our$module_name_rx=qr/[A-Z_a-z][0-9A-Z_a-z]*(?:::[0-9A-Z_a-z]+)*/;my$qual_module_spec_rx=qr#(?:/|::)[A-Z_a-z][0-9A-Z_a-z]*(?:(?:/|::)[0-9A-Z_a-z]+)*#;my$unqual_top_module_spec_rx=qr#[A-Z_a-z][0-9A-Z_a-z]*(?:(?:/|::)[0-9A-Z_a-z]+)*#;our$top_module_spec_rx=qr/$qual_module_spec_rx|$unqual_top_module_spec_rx/o;my$unqual_sub_module_spec_rx=qr#[0-9A-Z_a-z]+(?:(?:/|::)[0-9A-Z_a-z]+)*#;our$sub_module_spec_rx=qr/$qual_module_spec_rx|$unqual_sub_module_spec_rx/o;sub is_module_name($) {_is_string($_[0])&& $_[0]=~ /\A$module_name_rx\z/o}*is_valid_module_name=\&is_module_name;sub check_module_name($) {unless(&is_module_name){die +(_is_string($_[0])? "`$_[0]'" : "argument")." is not a module name\n"}}sub module_notional_filename($) {&check_module_name;my($name)=@_;$name =~ s!::!/!g;return$name.".pm"}BEGIN {*_WORK_AROUND_HINT_LEAKAGE="$]" < 5.011 &&!("$]" >= 5.009004 && "$]" < 5.010001)? sub(){1}: sub(){0};*_WORK_AROUND_BROKEN_MODULE_STATE="$]" < 5.009 ? sub(){1}: sub(){0}}BEGIN {if(_WORK_AROUND_BROKEN_MODULE_STATE){eval q{
sub Module::Runtime::__GUARD__::DESTROY {
delete $INC{$_[0]->[0]} if @{$_[0]};
}
1;
};die $@ if $@ ne ""}}sub require_module($) {local %^H if _WORK_AROUND_HINT_LEAKAGE;if(_WORK_AROUND_BROKEN_MODULE_STATE){my$notional_filename=&module_notional_filename;my$guard=bless([$notional_filename ],"Module::Runtime::__GUARD__");my$result=CORE::require($notional_filename);pop @$guard;return$result}else {return scalar(CORE::require(&module_notional_filename))}}sub use_module($;$) {my($name,$version)=@_;require_module($name);$name->VERSION($version)if @_ >= 2;return$name}sub use_package_optimistically($;$) {my($name,$version)=@_;my$fn=module_notional_filename($name);eval {local$SIG{__DIE__};require_module($name)};die $@ if $@ ne "" && ($@ !~ /\ACan't locate \Q$fn\E .+ at \Q@{[__FILE__]}\E line/s || $@ =~ /^Compilation\ failed\ in\ require
\ at\ \Q@{[__FILE__]}\E\ line/xm);$name->VERSION($version)if @_ >= 2;return$name}sub is_module_spec($$) {my($prefix,$spec)=@_;return _is_string($spec)&& $spec =~ ($prefix ? qr/\A$sub_module_spec_rx\z/o : qr/\A$top_module_spec_rx\z/o)}*is_valid_module_spec=\&is_module_spec;sub check_module_spec($$) {unless(&is_module_spec){die +(_is_string($_[1])? "`$_[1]'" : "argument")." is not a module specification\n"}}sub compose_module_name($$) {my($prefix,$spec)=@_;check_module_name($prefix)if defined$prefix;&check_module_spec;if($spec =~ s#\A(?:/|::)##){}else {$spec=$prefix."::".$spec if defined$prefix}$spec =~ s#/#::#g;return$spec}1;
MODULE_RUNTIME
$fatpacked{"Monitoring/Plugin.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN';
package Monitoring::Plugin;use Monitoring::Plugin::Functions qw(:codes %ERRORS %STATUS_TEXT @STATUS_CODES);use Params::Validate qw(:all);use 5.006;use strict;use warnings;use Carp;use base qw(Class::Accessor::Fast);Monitoring::Plugin->mk_accessors(qw(shortname perfdata messages opts threshold));use Exporter;our@ISA=qw(Exporter);our@EXPORT=(@STATUS_CODES);our@EXPORT_OK=qw(%ERRORS %STATUS_TEXT);our$VERSION="0.39";sub new {my$class=shift;my%args=validate(@_,{shortname=>0,usage=>0,version=>0,url=>0,plugin=>0,blurb=>0,extra=>0,license=>0,timeout=>0 },);my$shortname=Monitoring::Plugin::Functions::get_shortname(\%args);delete$args{shortname}if (exists$args{shortname});my$self={shortname=>$shortname,perfdata=>[],messages=>{warning=>[],critical=>[],ok=>[]},opts=>undef,threshold=>undef,};bless$self,$class;if (exists$args{usage}){require Monitoring::Plugin::Getopt;$self->opts(new Monitoring::Plugin::Getopt(%args))}return$self}sub add_perfdata {my ($self,%args)=@_;require Monitoring::Plugin::Performance;my$perf=Monitoring::Plugin::Performance->new(%args);push @{$self->perfdata},$perf}sub all_perfoutput {my$self=shift;return join(" ",map {$_->perfoutput}(@{$self->perfdata}))}sub set_thresholds {my$self=shift;require Monitoring::Plugin::Threshold;return$self->threshold(Monitoring::Plugin::Threshold->set_thresholds(@_))}sub plugin_exit {my$self=shift;Monitoring::Plugin::Functions::plugin_exit(@_,{plugin=>$self })}sub plugin_die {my$self=shift;Monitoring::Plugin::Functions::plugin_die(@_,{plugin=>$self })}sub nagios_exit {my$self=shift;Monitoring::Plugin::Functions::plugin_exit(@_,{plugin=>$self })}sub nagios_die {my$self=shift;Monitoring::Plugin::Functions::plugin_die(@_,{plugin=>$self })}sub die {my$self=shift;Monitoring::Plugin::Functions::plugin_die(@_,{plugin=>$self })}sub max_state {Monitoring::Plugin::Functions::max_state(@_)}sub max_state_alt {Monitoring::Plugin::Functions::max_state_alt(@_)}sub check_threshold {my$self=shift;my%args;if ($#_==0 && (!ref $_[0]|| ref $_[0]eq "ARRAY")){%args=(check=>shift)}else {%args=validate (@_,{check=>1,warning=>0,critical=>0,})}if (exists$args{warning}|| exists$args{critical}){$self->set_thresholds(warning=>$args{warning},critical=>$args{critical},)}elsif (defined$self->threshold){}elsif (defined$self->opts){$self->set_thresholds(warning=>$self->opts->warning,critical=>$self->opts->critical,)}else {return UNKNOWN}return$self->threshold->get_status($args{check})}sub add_arg {my$self=shift;$self->opts->arg(@_)if$self->_check_for_opts}sub getopts {my$self=shift;$self->opts->getopts(@_)if$self->_check_for_opts}sub _check_for_opts {my$self=shift;croak "You have to supply a 'usage' param to Monitoring::Plugin::new() if you want to use Getopts from your Monitoring::Plugin object." unless ref$self->opts()eq 'Monitoring::Plugin::Getopt';return$self}sub add_message {my$self=shift;my ($code,@messages)=@_;croak "Invalid error code '$code'" unless defined($ERRORS{uc$code})|| defined($STATUS_TEXT{$code});$code=$STATUS_TEXT{$code}if$STATUS_TEXT{$code};$code=lc$code;croak "Error code '$code' not supported by add_message" if$code eq 'unknown' || $code eq 'dependent';$self->messages($code,[])unless$self->messages->{$code};push @{$self->messages->{$code}},@messages}sub check_messages {my$self=shift;my%args=@_;for my$code (qw(critical warning ok)){my$messages=$self->messages->{$code}|| [];if ($args{$code}){unless (ref$args{$code}eq 'ARRAY'){if ($code eq 'ok'){$args{$code}=[$args{$code}]}else {croak "Invalid argument '$code'"}}push @{$args{$code}},@$messages}else {$args{$code}=$messages}}Monitoring::Plugin::Functions::check_messages(%args)}1;
MONITORING_PLUGIN
$fatpacked{"Monitoring/Plugin/Config.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_CONFIG';
package Monitoring::Plugin::Config;use 5.006;use strict;use warnings;use Carp;use File::Spec;use base qw(Config::Tiny);my$FILENAME1='plugins.ini';my$FILENAME2='nagios-plugins.ini';my$FILENAME3='monitoring-plugins.ini';my$CURRENT_FILE=undef;my@MONITORING_CONFIG_PATH=qw(/etc/nagios /usr/local/nagios/etc /usr/local/etc/nagios /etc/opt/nagios);my@CONFIG_PATH=qw(/etc /usr/local/etc /etc/opt);sub read {my$class=shift;unless ($_[0]){SEARCH: {if ($ENV{MONITORING_CONFIG_PATH}|| $ENV{NAGIOS_CONFIG_PATH}){for (split /:/,($ENV{MONITORING_CONFIG_PATH}|| $ENV{NAGIOS_CONFIG_PATH})){my$file=File::Spec->catfile($_,$FILENAME1);unshift(@_,$file),last SEARCH if -f $file;$file=File::Spec->catfile($_,$FILENAME2);unshift(@_,$file),last SEARCH if -f $file;$file=File::Spec->catfile($_,$FILENAME3);unshift(@_,$file),last SEARCH if -f $file}}for (@MONITORING_CONFIG_PATH){my$file=File::Spec->catfile($_,$FILENAME1);unshift(@_,$file),last SEARCH if -f $file}for (@CONFIG_PATH){my$file=File::Spec->catfile($_,$FILENAME2);unshift(@_,$file),last SEARCH if -f $file;$file=File::Spec->catfile($_,$FILENAME3);unshift(@_,$file),last SEARCH if -f $file}}die "Cannot find '$FILENAME1', '$FILENAME2' or '$FILENAME3' in any standard location.\n" unless $_[0]}$CURRENT_FILE=$_[0];$class->SUPER::read(@_)}sub read_string {my$class=ref $_[0]? ref shift : shift;my$self=bless {},$class;return undef unless defined $_[0];my$ns='_';my$counter=0;for (split /(?:\015{1,2}\012|\015|\012)/,shift){$counter++;next if /^\s*(?:\#|\;|$)/;if (/^\s*\[\s*(.+?)\s*\]\s*$/){$self->{$ns=$1}||= {};next}if (/^\s*([^=]+?)\s*=\s*(.*?)\s*$/){push @{$self->{$ns}->{$1}},$2;next}return$self->_error("Syntax error at line $counter: '$_'")}$self}sub write {croak "Write access not permitted"}sub mp_getfile {return$CURRENT_FILE}1;
MONITORING_PLUGIN_CONFIG
$fatpacked{"Monitoring/Plugin/ExitResult.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_EXITRESULT';
package Monitoring::Plugin::ExitResult;use 5.006;use strict;use warnings;use overload '""'=>sub {shift->{message}};sub new {my$class=shift;return bless {return_code=>$_[0],message=>$_[1]},$class}sub message {shift->{message}}sub return_code {shift->{return_code}}sub code {shift->{return_code}}1;
MONITORING_PLUGIN_EXITRESULT
$fatpacked{"Monitoring/Plugin/Functions.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_FUNCTIONS';
package Monitoring::Plugin::Functions;use 5.006;use strict;use warnings;use File::Basename;use Params::Validate qw(:types validate);use Math::Calc::Units;our$VERSION="0.39";our@STATUS_CODES=qw(OK WARNING CRITICAL UNKNOWN DEPENDENT);require Exporter;our@ISA=qw(Exporter);our@EXPORT=(@STATUS_CODES,qw(plugin_exit plugin_die check_messages));our@EXPORT_OK=qw(%ERRORS %STATUS_TEXT @STATUS_CODES get_shortname max_state max_state_alt convert $value_re);our%EXPORT_TAGS=(all=>[@EXPORT,@EXPORT_OK ],codes=>[@STATUS_CODES ],functions=>[qw(plugin_exit plugin_die check_messages max_state max_state_alt convert) ],);use constant OK=>0;use constant WARNING=>1;use constant CRITICAL=>2;use constant UNKNOWN=>3;use constant DEPENDENT=>4;our%ERRORS=('OK'=>OK,'WARNING'=>WARNING,'CRITICAL'=>CRITICAL,'UNKNOWN'=>UNKNOWN,'DEPENDENT'=>DEPENDENT,);our%STATUS_TEXT=reverse%ERRORS;my$value=qr/[-+]?[\d\.]+/;our$value_re=qr/$value(?:e$value)?/;my$_fake_exit=0;sub _fake_exit {@_ ? $_fake_exit=shift : $_fake_exit};my$_use_die=0;sub _use_die {@_ ? $_use_die=shift : $_use_die};sub get_shortname {my$arg=shift;my$shortname=undef;return$arg->{shortname}if (defined($arg->{shortname}));$shortname=$arg->{plugin}if (defined($arg->{plugin}));$shortname=uc basename($shortname || $ENV{PLUGIN_NAME}|| $ENV{NAGIOS_PLUGIN}|| $0);$shortname =~ s/^CHECK_(?:BY_)?//;$shortname =~ s/\..*$//;return$shortname}sub max_state {return CRITICAL if grep {$_==CRITICAL}@_;return WARNING if grep {$_==WARNING}@_;return OK if grep {$_==OK}@_;return UNKNOWN if grep {$_==UNKNOWN}@_;return DEPENDENT if grep {$_==DEPENDENT}@_;return UNKNOWN}sub max_state_alt {return CRITICAL if grep {$_==CRITICAL}@_;return WARNING if grep {$_==WARNING}@_;return UNKNOWN if grep {$_==UNKNOWN}@_;return DEPENDENT if grep {$_==DEPENDENT}@_;return OK if grep {$_==OK}@_;return UNKNOWN}sub plugin_exit {my ($code,$message,$arg)=@_;if (defined$code && ($code eq 'return_code' || $code eq 'message')){if (int(@_ / 2)!=@_ / 2 && ref $_[$#_]){$arg=pop @_}else {undef$arg}my%arg=@_;$code=$arg{return_code};$message=$arg{message}}$arg ||= {};$code=$ERRORS{$code}if defined$code && exists$ERRORS{$code};$code=UNKNOWN unless defined$code && exists$STATUS_TEXT{$code};$message='' unless defined$message;if (ref$message && ref$message eq 'ARRAY'){$message=join(' ',map {chomp;$_}@$message)}else {chomp$message}my$output="$STATUS_TEXT{$code}";$output .= " - $message" if defined$message && $message ne '';my$shortname=($arg->{plugin}? $arg->{plugin}->shortname : undef);$shortname ||= get_shortname();$output="$shortname $output" if$shortname;if ($arg->{plugin}){my$plugin=$arg->{plugin};$output .= " | ".$plugin->all_perfoutput if$plugin->perfdata && $plugin->all_perfoutput}$output .= "\n";if ($_fake_exit){require Monitoring::Plugin::ExitResult;return Monitoring::Plugin::ExitResult->new($code,$output)}_plugin_exit($code,$output)}sub _plugin_exit {my ($code,$output)=@_;if ($_use_die){for (my$i=0;;$i++){@_=caller($i);last unless @_;if ($_[3]=~ m/die/){$!=$code;die($output)}}}print$output;exit$code}sub plugin_die {my ($arg1,$arg2,$rest)=@_;if (defined$arg1 && ($arg1 eq 'return_code' || $arg1 eq 'message')){return plugin_exit(@_)}elsif (defined$arg1 && (exists$ERRORS{$arg1}|| exists$STATUS_TEXT{$arg1})){return plugin_exit(@_)}elsif (defined$arg2 && (exists$ERRORS{$arg2}|| exists$STATUS_TEXT{$arg2})){return plugin_exit($arg2,$arg1,$rest)}else {return plugin_exit(UNKNOWN,$arg1,$arg2)}}sub die {plugin_die(@_)}sub convert {my ($value,$from,$to)=@_;my ($newval)=Math::Calc::Units::convert("$value $from",$to,'exact');return$newval}sub check_messages {my%arg=validate(@_,{critical=>{type=>ARRAYREF },warning=>{type=>ARRAYREF },ok=>{type=>ARRAYREF | SCALAR,optional=>1 },'join'=>{default=>' ' },join_all=>0,});$arg{join}=' ' unless defined$arg{join};my$code=OK;$code ||= CRITICAL if @{$arg{critical}};$code ||= WARNING if @{$arg{warning}};return$code unless wantarray;my$message='';if ($arg{join_all}){$message=join($arg{join_all},map {@$_ ? join($arg{'join'},@$_): ()}$arg{critical},$arg{warning},$arg{ok}? (ref$arg{ok}? $arg{ok}: [$arg{ok}]): [])}else {$message ||= join($arg{'join'},@{$arg{critical}})if$code==CRITICAL;$message ||= join($arg{'join'},@{$arg{warning}})if$code==WARNING;$message ||= ref$arg{ok}? join($arg{'join'},@{$arg{ok}}): $arg{ok}if$arg{ok}}return ($code,$message)}1;
MONITORING_PLUGIN_FUNCTIONS
$fatpacked{"Monitoring/Plugin/Getopt.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_GETOPT';
package Monitoring::Plugin::Getopt;use 5.006;use strict;use warnings;use File::Basename;use Getopt::Long qw(:config no_ignore_case bundling);use Carp;use Params::Validate qw(:all);use base qw(Class::Accessor);use Monitoring::Plugin::Functions;use Monitoring::Plugin::Config;use vars qw($VERSION);$VERSION=$Monitoring::Plugin::Functions::VERSION;my%DEFAULT=(timeout=>15,verbose=>0,license=>"This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY.
It may be used, redistributed and/or modified under the terms of the GNU
General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt).",);my@ARGS=({spec=>'usage|?',help=>"-?, --usage\n Print usage information",},{spec=>'help|h',help=>"-h, --help\n Print detailed help screen",},{spec=>'version|V',help=>"-V, --version\n Print version information",},{spec=>'extra-opts:s@',help=>"--extra-opts=[section][\@file]\n Read options from an ini file. See https://www.monitoring-plugins.org/doc/extra-opts.html\n for usage and examples.",},{spec=>'timeout|t=i',help=>"-t, --timeout=INTEGER\n Seconds before plugin times out (default: %s)",default=>$DEFAULT{timeout},},{spec=>'verbose|v+',help=>"-v, --verbose\n Show details for command-line debugging (can repeat up to 3 times)",default=>$DEFAULT{verbose},},);my%DEFER_ARGS=map {$_=>1}qw(timeout verbose);sub _die {my$self=shift;my ($msg)=@_;$msg .= "\n" unless substr($msg,-1)eq "\n";Monitoring::Plugin::Functions::_plugin_exit(3,$msg)}sub _attr {my$self=shift;my ($item,$extra)=@_;$extra='' unless defined$extra;return '' unless$self->{_attr}->{$item};$self->{_attr}->{$item}."\n" .$extra}sub _spec_to_help {my ($self,$spec,$label)=@_;my ($opts,$type)=split /=|:/,$spec,2;my$optional=($spec =~ m/:/);my (@short,@long);for (split /\|/,$opts){if (length $_==1){push@short,"-$_"}else {push@long,"--$_"}}my$help=join(', ',@short,@long);if ($type){if (!$label){if ($type eq 'i' || $type eq '+' || $type =~ /\d+/){$label='INTEGER'}else {$label='STRING'}}if ($optional){$help .= '[=' .$label .']'}else {$help .= '=' .$label}}elsif ($label){carp "Label specified, but there's no type in spec '$spec'"}$help .= "\n ";return$help}sub _options {my$self=shift;my@args=();my@defer=();for (@{$self->{_args}}){if (exists$DEFER_ARGS{$_->{name}}){push@defer,$_}else {push@args,$_}}my@options=();for my$arg (@args,@defer){my$help_array=ref$arg->{help}&& ref$arg->{help}eq 'ARRAY' ? $arg->{help}: [$arg->{help}];my$label_array=$arg->{label}&& ref$arg->{label}&& ref$arg->{label}eq 'ARRAY' ? $arg->{label}: [$arg->{label}];my$help_string='';for (my$i=0;$i <= $#$help_array;$i++){my$help=$help_array->[$i];if ($help =~ m/^\s*-/){$help_string .= $help}else {$help_string .= $self->_spec_to_help($arg->{spec},$label_array->[$i]).$help;$help_string .= "\n " if$i < $#$help_array}}if ($help_string =~ m/%s/){my$default=defined$arg->{default}? $arg->{default}: '';my$replaced=$help_string;$replaced =~ s|%s|$default|gmx;push@options,$replaced}else {push@options,$help_string}}return ' ' .join("\n ",@options)}sub _usage {my$self=shift;my$usage=$self->_attr('usage');$usage =~ s|%s|$self->{_attr}->{plugin}|gmx;return($usage)}sub _revision {my$self=shift;my$revision=sprintf "%s %s",$self->{_attr}->{plugin},$self->{_attr}->{version};$revision .= sprintf " [%s]",$self->{_attr}->{url}if$self->{_attr}->{url};$revision .= "\n";$revision}sub _help {my$self=shift;my$help='';$help .= $self->_revision ."\n";$help .= $self->_attr('license',"\n");$help .= $self->_attr('blurb',"\n");$help .= $self->_usage ? $self->_usage ."\n" : '';$help .= $self->_options ? $self->_options ."\n" : '';$help .= $self->_attr('extra',"\n");return$help}sub _process_specs_getopt_long {my$self=shift;my@opts=();for my$arg (@{$self->{_args}}){push@opts,$arg->{spec};my$spec=$arg->{spec};$spec =~ s/[=:].*$//;my$name=(split /\s*\|\s*/,$spec)[0];$arg->{name}=$name;if (defined$self->{$name}){$arg->{default}=$self->{$name}}else {$self->{$name}=$arg->{default}}}return@opts}sub _check_required_opts {my$self=shift;my@missing=();for my$arg (@{$self->{_args}}){if ($arg->{required}&&!defined$self->{$arg->{name}}){push@missing,$arg->{name}}}if (@missing){$self->_die($self->_usage ."\n" .join("\n",map {sprintf "Missing argument: %s",$_}@missing)."\n")}}sub _process_opts {my$self=shift;$self->_die($self->_usage)if$self->{usage};$self->_die($self->_revision)if$self->{version};$self->_die($self->_help)if$self->{help}}sub _load_config_section {my$self=shift;my ($section,$file,$flags)=@_;$section ||= $self->{_attr}->{plugin};my$Config;eval {$Config=Monitoring::Plugin::Config->read($file)};$self->_die($@)if ($@);$file ||= $Config->mp_getfile();$self->_die("Invalid section '$section' in config file '$file'")unless exists$Config->{$section};return$Config->{$section}}sub _setup_spec_index {my$self=shift;return if defined$self->{_spec};$self->{_spec}={map {$_->{name}=>$_->{spec}}@{$self->{_args}}}}sub _cmdline_value {my$self=shift;local $_=shift;if (m/\s/ && (m/^[^"']/ || m/[^"']$/)){return qq("$_")}elsif ($_ eq ''){return q("")}else {return $_}}sub _cmdline {my$self=shift;my ($hash)=@_;$hash ||= $self;$self->_setup_spec_index;my@args=();for my$key (sort keys %$hash){next if$key =~ m/^_/;next if exists$DEFAULT{$key}&& $hash->{$key}eq $DEFAULT{$key};next if grep {$key eq $_}qw(help usage version extra-opts);next unless defined$hash->{$key};my$spec=$self->{_spec}->{$key}|| '';if ($spec =~ m/[=:].+$/){for my$value (ref$hash->{$key}eq 'ARRAY' ? @{$hash->{$key}}: ($hash->{$key})){$value=$self->_cmdline_value($value);if (length($key)> 1){push@args,sprintf "--%s=%s",$key,$value}else {push@args,"-$key",$value}}}else {push@args,(length($key)> 1 ? '--' : '-').$key}}return wantarray ? @args : join(' ',@args)}sub _process_extra_opts {my$self=shift;my ($args)=@_;my$extopts_list=$args->{'extra-opts'};my@sargs=();for my$extopts (@$extopts_list){$extopts ||= $self->{_attr}->{plugin};my$section=$extopts;my$file='';if ($extopts =~ m/^([^@]*)@(.*?)\s*$/){$section=$1;$file=$2}my$shash=$self->_load_config_section($section,$file);push@sargs,$self->_cmdline($shash)}@ARGV=(@sargs,@{$self->{_attr}->{argv}});printf "[extra-opts] %s %s\n",$self->{_attr}->{plugin},join(' ',@ARGV)if$args->{verbose}&& $args->{verbose}>= 3}sub arg {my$self=shift;my%args;if ($_[0]=~ m/^(spec|help|required|default)$/ && scalar(@_)% 2==0){%args=validate(@_,{spec=>1,help=>1,default=>0,required=>0,label=>0,})}else {my@args=validate_pos(@_,1,1,0,0,0);%args=(spec=>$args[0],help=>$args[1],default=>$args[2],required=>$args[3],label=>$args[4],)}push @{$self->{_args}},\%args}sub getopts {my$self=shift;my@opt_array=$self->_process_specs_getopt_long;$self->{_attr}->{argv}=[@ARGV ];my$args1={};my$ok=GetOptions($args1,@opt_array);$self->_die($self->_usage)unless$ok;$self->_process_extra_opts($args1);$ok=GetOptions($self,@opt_array);$self->_die($self->_usage)unless$ok;$self->_process_opts;$self->_check_required_opts;$self->mk_ro_accessors(grep!/^_/,keys %$self);$SIG{ALRM}=sub {my$plugin=uc$self->{_attr}->{plugin};$plugin =~ s/^check_//;$self->_die(sprintf("%s UNKNOWN - plugin timed out (timeout %ss)",$plugin,$self->timeout))}}sub _init {my$self=shift;my$plugin=basename($ENV{PLUGIN_NAME}|| $ENV{NAGIOS_PLUGIN}|| $0);my%attr=validate(@_,{usage=>1,version=>0,url=>0,plugin=>{default=>$plugin },blurb=>0,extra=>0,'extra-opts'=>0,license=>{default=>$DEFAULT{license}},timeout=>{default=>$DEFAULT{timeout}},});$self->{timeout}=delete$attr{timeout};$self->{_attr}={%attr };chomp foreach values %{$self->{_attr}};$self->{_args}=[@ARGS ];$self}sub new {my$class=shift;my$self=bless {},$class;$self->_init(@_)}1;
MONITORING_PLUGIN_GETOPT
$fatpacked{"Monitoring/Plugin/Performance.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_PERFORMANCE';
package Monitoring::Plugin::Performance;use 5.006;use strict;use warnings;use Carp;use base qw(Class::Accessor::Fast);__PACKAGE__->mk_ro_accessors(qw(label value uom warning critical min max));use Monitoring::Plugin::Functions;use Monitoring::Plugin::Threshold;use Monitoring::Plugin::Range;our ($VERSION)=$Monitoring::Plugin::Functions::VERSION;sub import {my ($class,%attr)=@_;$_=$attr{use_die}|| 0;Monitoring::Plugin::Functions::_use_die($_)}my$value=qr/[-+]?[\d\.,]+/;my$value_re=qr/$value(?:e$value)?/;my$value_with_negative_infinity=qr/$value_re|~/;sub _parse {my$class=shift;my$string=shift;$string =~ /^'?([^'=]+)'?=($value_re)([\w%]*);?($value_with_negative_infinity\:?$value_re?)?;?($value_with_negative_infinity\:?$value_re?)?;?($value_re)?;?($value_re)?/o;return undef unless ((defined $1 && $1 ne "")&& (defined $2 && $2 ne ""));my@info=($1,$2,$3,$4,$5,$6,$7);map {defined$info[$_]&& $info[$_]=~ s/,/./go}(1,3,4,5,6);my$performance_value;{my$not_value;local$SIG{__WARN__}=sub {$not_value++};$performance_value=$info[1]+0;return undef if$not_value}my$p=$class->new(label=>$info[0],value=>$performance_value,uom=>$info[2],warning=>$info[3],critical=>$info[4],min=>$info[5],max=>$info[6]);return$p}sub _nvl {my ($self,$value)=@_;defined$value ? $value : ''}sub perfoutput {my$self=shift;my$label=$self->label;if ($label =~ / /){$label="'$label'"}my$out=sprintf "%s=%s%s;%s;%s;%s;%s",$label,$self->value,$self->_nvl($self->uom),$self->_nvl($self->warning),$self->_nvl($self->critical),$self->_nvl($self->min),$self->_nvl($self->max);$out =~ s/;;$//;return$out}sub parse_perfstring {my ($class,$perfstring)=@_;my@perfs=();my$obj;while ($perfstring){$perfstring =~ s/^\s*//;if (@{[$perfstring =~ /=/g]}> 1){$perfstring =~ s/^(.*?=.*?)\s//;if (defined $1){$obj=$class->_parse($1)}else {$perfstring="";$obj=$class->_parse($perfstring)}}else {$obj=$class->_parse($perfstring);$perfstring=""}push@perfs,$obj if$obj}return@perfs}sub rrdlabel {my$self=shift;my$name=$self->clean_label;return substr($name,0,19)}sub clean_label {my$self=shift;my$name=$self->label;if ($name eq "/"){$name="root"}elsif ($name =~ s/^\///){$name =~ s/\//_/g}$name =~ s/\W/_/g;return$name}sub threshold {my$self=shift;return Monitoring::Plugin::Threshold->set_thresholds(warning=>$self->warning,critical=>$self->critical)}sub new {my$class=shift;my%arg=@_;if (my$threshold=delete$arg{threshold}){$arg{warning}||= $threshold->warning ."";$arg{critical}||= $threshold->critical .""}$class->SUPER::new(\%arg)}1;
MONITORING_PLUGIN_PERFORMANCE
$fatpacked{"Monitoring/Plugin/Range.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_RANGE';
package Monitoring::Plugin::Range;use 5.006;use strict;use warnings;use Carp;use base qw(Class::Accessor::Fast);__PACKAGE__->mk_accessors(qw(start end start_infinity end_infinity alert_on));use Monitoring::Plugin::Functions qw(:DEFAULT $value_re);our ($VERSION)=$Monitoring::Plugin::Functions::VERSION;use overload 'eq'=>sub {shift->_stringify},'""'=>sub {shift->_stringify};use constant OUTSIDE=>0;use constant INSIDE=>1;sub _stringify {my$self=shift;return "" unless$self->is_set;return (($self->alert_on)? "@" : "").(($self->start_infinity==1)? "~:" : (($self->start==0)?"":$self->start.":")).(($self->end_infinity==1)? "" : $self->end)}sub is_set {my$self=shift;(!defined$self->alert_on)? 0 : 1}sub _set_range_start {my ($self,$value)=@_;$self->start($value+0);$self->start_infinity(0)}sub _set_range_end {my ($self,$value)=@_;$self->end($value+0);$self->end_infinity(0)}sub parse_range_string {my ($class,$string)=@_;my$valid=0;my$range=$class->new(start=>0,start_infinity=>0,end=>0,end_infinity=>1,alert_on=>OUTSIDE);$string =~ s/\s//g;unless ($string =~ /[\d~]/ && $string =~ m/^\@?($value_re|~)?(:($value_re)?)?$/){carp "invalid range definition '$string'";return undef}if ($string =~ s/^\@//){$range->alert_on(INSIDE)}if ($string =~ s/^~//){$range->start_infinity(1)}if ($string =~ m/^($value_re)?:/){my$start=$1;$range->_set_range_start($start)if defined$start;$range->end_infinity(1);$string =~ s/^($value_re)?://;$valid++}if ($string =~ /^($value_re)$/){$range->_set_range_end($string);$valid++}if ($valid && ($range->start_infinity==1 || $range->end_infinity==1 || $range->start <= $range->end)){return$range}return undef}sub check_range {my ($self,$value)=@_;my$false=0;my$true=1;if ($self->alert_on==INSIDE){$false=1;$true=0}if ($self->end_infinity==0 && $self->start_infinity==0){if ($self->start <= $value && $value <= $self->end){return$false}else {return$true}}elsif ($self->start_infinity==0 && $self->end_infinity==1){if ($value >= $self->start){return$false}else {return$true}}elsif ($self->start_infinity==1 && $self->end_infinity==0){if ($value <= $self->end){return$false}else {return$true}}else {return$false}}sub new {shift->SUPER::new({@_})}1;
MONITORING_PLUGIN_RANGE
$fatpacked{"Monitoring/Plugin/Threshold.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MONITORING_PLUGIN_THRESHOLD';
package Monitoring::Plugin::Threshold;use 5.006;use strict;use warnings;use base qw(Class::Accessor::Fast);__PACKAGE__->mk_accessors(qw(warning critical));use Monitoring::Plugin::Range;use Monitoring::Plugin::Functions qw(:codes plugin_die);our ($VERSION)=$Monitoring::Plugin::Functions::VERSION;sub get_status {my ($self,$value)=@_;$value=[$value ]if (ref$value eq "");for my$v (@$value){if ($self->critical->is_set){return CRITICAL if$self->critical->check_range($v)}}for my$v (@$value){if ($self->warning->is_set){return WARNING if$self->warning->check_range($v)}}return OK}sub _inflate {my ($self,$value,$key)=@_;return Monitoring::Plugin::Range->new if!defined$value;if (ref$value){plugin_die("Invalid $key object: type " .ref$value)unless$value->isa("Monitoring::Plugin::Range");return$value}return Monitoring::Plugin::Range->new if$value eq "";my$range=Monitoring::Plugin::Range->parse_range_string($value);plugin_die("Cannot parse $key range: '$value'")unless(defined($range));return$range}sub set_thresholds {my ($self,%arg)=@_;return$self->new(%arg)unless ref$self;$self->set($_,$arg{$_})foreach qw(warning critical)}sub set {my$self=shift;my ($key,$value)=@_;$self->SUPER::set($key,$self->_inflate($value,$key))}sub new {my ($self,%arg)=@_;$self->SUPER::new({map {$_=>$self->_inflate($arg{$_},$_)}qw(warning critical)})}1;
MONITORING_PLUGIN_THRESHOLD
$fatpacked{"Params/Validate.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATE';
package Params::Validate;use 5.008001;use strict;use warnings;our$VERSION='1.26';use Exporter;use Module::Implementation;use Params::Validate::Constants;use vars qw($NO_VALIDATION %OPTIONS $options);our@ISA='Exporter';my@types=qw(SCALAR ARRAYREF HASHREF CODEREF GLOB GLOBREF SCALARREF HANDLE BOOLEAN UNDEF OBJECT);our%EXPORT_TAGS=('all'=>[qw(validate validate_pos validation_options validate_with),@types ],types=>\@types,);our@EXPORT_OK=(@{$EXPORT_TAGS{all}},'set_options');our@EXPORT=qw(validate validate_pos);$NO_VALIDATION=$ENV{PERL_NO_VALIDATION};{my$loader=Module::Implementation::build_loader_sub(implementations=>['XS','PP' ],symbols=>[qw(validate validate_pos validate_with validation_options set_options),],);$ENV{PARAMS_VALIDATE_IMPLEMENTATION}='PP' if$ENV{PV_TEST_PERL};$loader->()}1;
PARAMS_VALIDATE
$fatpacked{"Params/Validate/Constants.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATE_CONSTANTS';
package Params::Validate::Constants;use strict;use warnings;our$VERSION='1.26';our@ISA='Exporter';our@EXPORT=qw(SCALAR ARRAYREF HASHREF CODEREF GLOB GLOBREF SCALARREF HANDLE BOOLEAN UNDEF OBJECT UNKNOWN);sub SCALAR () {1}sub ARRAYREF () {2}sub HASHREF () {4}sub CODEREF () {8}sub GLOB () {16}sub GLOBREF () {32}sub SCALARREF () {64}sub UNKNOWN () {128}sub UNDEF () {256}sub OBJECT () {512}sub HANDLE () {16 | 32}sub BOOLEAN () {1 | 256}1;
PARAMS_VALIDATE_CONSTANTS
$fatpacked{"Params/Validate/PP.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATE_PP';
package Params::Validate::PP;use strict;use warnings;our$VERSION='1.26';use Params::Validate::Constants;use Scalar::Util 1.10 ();our$options;sub validate_pos (\@@) {return if$Params::Validate::NO_VALIDATION &&!defined wantarray;my$p=shift;my@specs=@_;my@p=@$p;if ($Params::Validate::NO_VALIDATION){for (my$x=$#p + 1;$x <= $#specs;$x++ ){$p[$x]=$specs[$x]->{default}if ref$specs[$x]&& exists$specs[$x]->{default}}return wantarray ? @p : \@p}local$options ||= _get_options((caller(0))[0])unless defined$options;my$min=0;while (1){last unless (ref$specs[$min]?!(exists$specs[$min]->{default}|| $specs[$min]->{optional}): $specs[$min]);$min++}my$max=scalar@specs;my$actual=scalar@p;unless ($actual >= $min && ($options->{allow_extra}|| $actual <= $max)){my$minmax=($options->{allow_extra}? "at least $min" : ($min!=$max ? "$min - $max" : $max));my$val=$options->{allow_extra}? $min : $max;$minmax .= $val!=1 ? ' were' : ' was';my$called=_get_called();$options->{on_fail}->("$actual parameter" .($actual!=1 ? 's' : '')." " .($actual!=1 ? 'were' : 'was')." passed to $called but $minmax expected\n")}my$bigger=$#p > $#specs ? $#p : $#specs;for (0 .. $bigger){my$spec=$specs[$_];next unless ref$spec;if ($_ <= $#p){_validate_one_param($p[$_],\@p,$spec,'Parameter #' .($_ + 1).' (%s)')}$p[$_]=$spec->{default}if $_ > $#p && exists$spec->{default}}_validate_pos_depends(\@p,\@specs);for (grep {defined$p[$_]&&!ref$p[$_]&& ref$specs[$_]&& $specs[$_]{untaint}}0 .. $bigger){($p[$_])=$p[$_]=~ /(.+)/}return wantarray ? @p : \@p}sub _validate_pos_depends {my ($p,$specs)=@_;for my$p_idx (0 .. $#$p){my$spec=$specs->[$p_idx];next unless$spec && UNIVERSAL::isa($spec,'HASH')&& exists$spec->{depends};my$depends=$spec->{depends};if (ref$depends){require Carp;local$Carp::CarpLevel=2;Carp::croak("Arguments to 'depends' for validate_pos() must be a scalar")}my$p_size=scalar @$p;if ($p_size < $depends - 1){my$error =("Parameter #" .($p_idx + 1)." depends on parameter #" .$depends .", which was not given");$options->{on_fail}->($error)}}return 1}sub _validate_named_depends {my ($p,$specs)=@_;for my$pname (keys %$p){my$spec=$specs->{$pname};next unless$spec && UNIVERSAL::isa($spec,'HASH')&& $spec->{depends};unless (UNIVERSAL::isa($spec->{depends},'ARRAY')||!ref$spec->{depends}){require Carp;local$Carp::CarpLevel=2;Carp::croak("Arguments to 'depends' must be a scalar or arrayref")}for my$depends_name (ref$spec->{depends}? @{$spec->{depends}}: $spec->{depends}){unless (exists$p->{$depends_name}){my$error =("Parameter '$pname' depends on parameter '" .$depends_name ."', which was not given");$options->{on_fail}->($error)}}}}sub validate (\@$) {return if$Params::Validate::NO_VALIDATION &&!defined wantarray;my$p=$_[0];my$specs=$_[1];local$options=_get_options((caller(0))[0])unless defined$options;if (ref$p eq 'ARRAY'){if (ref$p->[0]){$p={%{$p->[0]}}}elsif (@$p % 2){my$called=_get_called();$options->{on_fail}->("Odd number of parameters in call to $called " ."when named parameters were expected\n")}else {$p={@$p}}}if ($options->{normalize_keys}){$specs=_normalize_callback($specs,$options->{normalize_keys});$p=_normalize_callback($p,$options->{normalize_keys})}elsif ($options->{ignore_case}|| $options->{strip_leading}){$specs=_normalize_named($specs);$p=_normalize_named($p)}if ($Params::Validate::NO_VALIDATION){return (wantarray ? ((map {$_=>$specs->{$_}->{default}}grep {ref$specs->{$_}&& exists$specs->{$_}->{default}}keys %$specs),(ref$p eq 'ARRAY' ? (ref$p->[0]? %{$p->[0]}: @$p): %$p)): do {my$ref=(ref$p eq 'ARRAY' ? (ref$p->[0]? $p->[0]: {@$p}): $p);for (grep {ref$specs->{$_}&& exists$specs->{$_}->{default}}keys %$specs){$ref->{$_}=$specs->{$_}->{default}unless exists$ref->{$_}}return$ref})}_validate_named_depends($p,$specs);unless ($options->{allow_extra}){if (my@unmentioned=grep {!exists$specs->{$_}}keys %$p){my$called=_get_called();$options->{on_fail}->("The following parameter" .(@unmentioned > 1 ? 's were' : ' was')." passed in the call to $called but " .(@unmentioned > 1 ? 'were' : 'was')." not listed in the validation options: @unmentioned\n")}}my@missing;keys %$specs;OUTER: while (my ($key,$spec)=each %$specs){if (!exists$p->{$key}&& (ref$spec ?!(do {if (exists$spec->{default}){$p->{$key}=$spec->{default};next OUTER}}|| do {next OUTER if$spec->{optional}}): $spec)){push@missing,$key}elsif (ref$spec){my$value=defined$p->{$key}? qq|"$p->{$key}"| : 'undef';_validate_one_param($p->{$key},$p,$spec,qq{The '$key' parameter (%s)})}}if (@missing){my$called=_get_called();my$missing=join ', ',map {"'$_'"}@missing;$options->{on_fail}->("Mandatory parameter" .(@missing > 1 ? 's' : '')." $missing missing in call to $called\n")}for my$key (grep {defined$p->{$_}&&!ref$p->{$_}&& ref$specs->{$_}&& $specs->{$_}{untaint}}keys %$p){($p->{$key})=$p->{$key}=~ /(.+)/}return wantarray ? %$p : $p}sub validate_with {return if$Params::Validate::NO_VALIDATION &&!defined wantarray;my%p=@_;local$options=_get_options((caller(0))[0],%p);unless ($Params::Validate::NO_VALIDATION){unless (exists$options->{called}){$options->{called}=(caller($options->{stack_skip}))[3]}}if (UNIVERSAL::isa($p{spec},'ARRAY')){return validate_pos(@{$p{params}},@{$p{spec}})}else {return&validate($p{params},$p{spec})}}sub _normalize_callback {my ($p,$func)=@_;my%new;for my$key (keys %$p){my$new_key=$func->($key);unless (defined$new_key){die "The normalize_keys callback did not return a defined value when normalizing the key '$key'"}if (exists$new{$new_key}){die "The normalize_keys callback returned a key that already exists, '$new_key', when normalizing the key '$key'"}$new{$new_key}=$p->{$key}}return \%new}sub _normalize_named {my%h=(ref $_[0])=~ /ARRAY/ ? @{$_[0]}: %{$_[0]};if ($options->{ignore_case}){$h{lc $_ }=delete$h{$_}for keys%h}if ($options->{strip_leading}){for my$key (keys%h){my$new;($new=$key)=~ s/^\Q$options->{strip_leading}\E//;$h{$new}=delete$h{$key}}}return \%h}my%Valid=map {$_=>1}qw(callbacks can default depends isa optional regex type untaint);sub _validate_one_param {my ($value,$params,$spec,$id)=@_;if (exists$spec->{type}){unless (defined$spec->{type}&& Scalar::Util::looks_like_number($spec->{type})&& $spec->{type}> 0){my$msg ="$id has a type specification which is not a number. It is ";if (defined$spec->{type}){$msg .= "a string - $spec->{type}"}else {$msg .= "undef"}$msg .= ".\n Use the constants exported by Params::Validate to declare types.";$options->{on_fail}->(sprintf($msg,_stringify($value)))}unless (_get_type($value)& $spec->{type}){my$type=_get_type($value);my@is=_typemask_to_strings($type);my@allowed=_typemask_to_strings($spec->{type});my$article=$is[0]=~ /^[aeiou]/i ? 'an' : 'a';my$called=_get_called(1);$options->{on_fail}->(sprintf("$id to $called was $article '@is', which " ."is not one of the allowed types: @allowed\n",_stringify($value)))}}return unless ($spec->{isa}|| $spec->{can}|| $spec->{callbacks}|| $spec->{regex});if (exists$spec->{isa}){for (ref$spec->{isa}? @{$spec->{isa}}: $spec->{isa}){unless (do {local $@=q{};eval {$value->isa($_)}}){my$is=ref$value ? ref$value : 'plain scalar';my$article1=$_ =~ /^[aeiou]/i ? 'an' : 'a';my$article2=$is =~ /^[aeiou]/i ? 'an' : 'a';my$called=_get_called(1);$options->{on_fail}->(sprintf("$id to $called was not $article1 '$_' " ."(it is $article2 $is)\n",_stringify($value)))}}}if (exists$spec->{can}){for (ref$spec->{can}? @{$spec->{can}}: $spec->{can}){unless (do {local $@=q{};eval {$value->can($_)}}){my$called=_get_called(1);$options->{on_fail}->(sprintf("$id to $called does not have the method: '$_'\n",_stringify($value)))}}}if ($spec->{callbacks}){unless (UNIVERSAL::isa($spec->{callbacks},'HASH')){my$called=_get_called(1);$options->{on_fail}->("'callbacks' validation parameter for $called must be a hash reference\n")}for (keys %{$spec->{callbacks}}){unless (UNIVERSAL::isa($spec->{callbacks}{$_},'CODE')){my$called=_get_called(1);$options->{on_fail}->("callback '$_' for $called is not a subroutine reference\n")}my$ok;my$e=do {local $@=q{};local$SIG{__DIE__};$ok=eval {$spec->{callbacks}{$_}->($value,$params)};$@};if (!$ok){my$called=_get_called(1);if (ref$e){$options->{on_fail}->($e)}else {my$msg="$id to $called did not pass the '$_' callback";$msg .= ": $e" if length$e;$msg .= "\n";$options->{on_fail}->(sprintf($msg,_stringify($value)))}}}}if (exists$spec->{regex}){unless ((defined$value ? $value : '')=~ /$spec->{regex}/){my$called=_get_called(1);$options->{on_fail}->(sprintf("$id to $called did not pass regex check\n",_stringify($value)))}}}{my%isas=('ARRAY'=>ARRAYREF,'HASH'=>HASHREF,'CODE'=>CODEREF,'GLOB'=>GLOBREF,'SCALAR'=>SCALARREF,'REGEXP'=>SCALARREF,);my%simple_refs=map {$_=>1}keys%isas;sub _get_type {return UNDEF unless defined $_[0];my$ref=ref $_[0];unless ($ref){return GLOB if UNIVERSAL::isa(\$_[0],'GLOB');return SCALAR}return$isas{$ref}if$simple_refs{$ref};for (keys%isas){return$isas{$_}| OBJECT if UNIVERSAL::isa($_[0],$_)}return UNKNOWN}}{my%type_to_string=(SCALAR()=>'scalar',ARRAYREF()=>'arrayref',HASHREF()=>'hashref',CODEREF()=>'coderef',GLOB()=>'glob',GLOBREF()=>'globref',SCALARREF()=>'scalarref',UNDEF()=>'undef',OBJECT()=>'object',UNKNOWN()=>'unknown',);sub _typemask_to_strings {my$mask=shift;my@types;for (SCALAR,ARRAYREF,HASHREF,CODEREF,GLOB,GLOBREF,SCALARREF,UNDEF,OBJECT,UNKNOWN){push@types,$type_to_string{$_}if$mask & $_}return@types ? @types : ('unknown')}}{my%defaults=(ignore_case=>0,strip_leading=>0,allow_extra=>0,on_fail=>sub {require Carp;Carp::croak($_[0])},stack_skip=>1,normalize_keys=>undef,);*set_options=\&validation_options;sub validation_options {my%opts=@_;my$caller=caller;for (keys%defaults){$opts{$_}=$defaults{$_}unless exists$opts{$_}}$Params::Validate::OPTIONS{$caller}=\%opts}sub _get_options {my$caller=shift;if (@_){return ($Params::Validate::OPTIONS{$caller}? {%{$Params::Validate::OPTIONS{$caller}},@_ }: {%defaults,@_ })}else {return (exists$Params::Validate::OPTIONS{$caller}? $Params::Validate::OPTIONS{$caller}: \%defaults)}}}sub _get_called {my$extra_skip=$_[0]|| 0;$extra_skip++;my$called=(exists$options->{called}? $options->{called}: (caller($options->{stack_skip}+ $extra_skip))[3]);$called='(unknown)' unless defined$called;return$called}sub _stringify {return defined $_[0]? qq{"$_[0]"} : 'undef'}1;
PARAMS_VALIDATE_PP
$fatpacked{"Params/Validate/XS.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATE_XS';
package Params::Validate::XS;use strict;use warnings;our$VERSION='1.26';use Carp;my$default_fail=sub {Carp::confess($_[0])};{my%defaults=(ignore_case=>0,strip_leading=>0,allow_extra=>0,on_fail=>$default_fail,stack_skip=>1,normalize_keys=>undef,);*set_options=\&validation_options;sub validation_options {my%opts=@_;my$caller=caller;for (keys%defaults){$opts{$_}=$defaults{$_}unless exists$opts{$_}}$Params::Validate::OPTIONS{$caller}=\%opts}use XSLoader;XSLoader::load(__PACKAGE__,exists$Params::Validate::XS::{VERSION}? ${$Params::Validate::XS::{VERSION}}: (),)}sub _check_regex_from_xs {return (defined $_[0]? $_[0]: '')=~ /$_[1]/ ? 1 : 0}1;
PARAMS_VALIDATE_XS
$fatpacked{"Params/ValidatePP.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATEPP';
package Params::Validate;our$VERSION='1.26';BEGIN {$ENV{PARAMS_VALIDATE_IMPLEMENTATION}='PP'}use Params::Validate;1;
PARAMS_VALIDATEPP
$fatpacked{"Params/ValidateXS.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PARAMS_VALIDATEXS';
package Params::Validate;our$VERSION='1.26';BEGIN {$ENV{PARAMS_VALIDATE_IMPLEMENTATION}='XS'}use Params::Validate;1;
PARAMS_VALIDATEXS
$fatpacked{"Try/Tiny.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'TRY_TINY';
package Try::Tiny;use 5.006;our$VERSION='0.27';use strict;use warnings;use Exporter 5.57 'import';our@EXPORT=our@EXPORT_OK=qw(try catch finally);use Carp;$Carp::Internal{+__PACKAGE__}++;BEGIN {my$su=$INC{'Sub/Util.pm'}&& defined&Sub::Util::set_subname;my$sn=$INC{'Sub/Name.pm'}&& eval {Sub::Name->VERSION(0.08)};unless ($su || $sn){$su=eval {require Sub::Util}&& defined&Sub::Util::set_subname;unless ($su){$sn=eval {require Sub::Name;Sub::Name->VERSION(0.08)}}}*_subname=$su ? \&Sub::Util::set_subname : $sn ? \&Sub::Name::subname : sub {$_[1]};*_HAS_SUBNAME=($su || $sn)? sub(){1}: sub(){0}}my%_finally_guards;sub try (&;@) {my ($try,@code_refs)=@_;my$wantarray=wantarray;my ($catch,@finally)=();for my$code_ref (@code_refs){if (ref($code_ref)eq 'Try::Tiny::Catch'){croak 'A try() may not be followed by multiple catch() blocks' if$catch;$catch=${$code_ref}}elsif (ref($code_ref)eq 'Try::Tiny::Finally'){push@finally,${$code_ref}}else {croak('try() encountered an unexpected argument (' .(defined$code_ref ? $code_ref : 'undef').') - perhaps a missing semi-colon before or')}}my$caller=caller;_subname("${caller}::try {...} "=>$try)if _HAS_SUBNAME;local$_finally_guards{guards}=[map {Try::Tiny::ScopeGuard->_new($_)}@finally ];my$prev_error=$@;my (@ret,$error);my$failed=not eval {$@=$prev_error;if ($wantarray){@ret=$try->()}elsif (defined$wantarray){$ret[0]=$try->()}else {$try->()};return 1};$error=$@;$@=$prev_error;if ($failed){push @$_,$error for @{$_finally_guards{guards}};if ($catch){for ($error){return$catch->($error)}}return}else {return$wantarray ? @ret : $ret[0]}}sub catch (&;@) {my ($block,@rest)=@_;croak 'Useless bare catch()' unless wantarray;my$caller=caller;_subname("${caller}::catch {...} "=>$block)if _HAS_SUBNAME;return (bless(\$block,'Try::Tiny::Catch'),@rest,)}sub finally (&;@) {my ($block,@rest)=@_;croak 'Useless bare finally()' unless wantarray;my$caller=caller;_subname("${caller}::finally {...} "=>$block)if _HAS_SUBNAME;return (bless(\$block,'Try::Tiny::Finally'),@rest,)}{package Try::Tiny::ScopeGuard;use constant UNSTABLE_DOLLARAT=>("$]" < '5.013002')? 1 : 0;sub _new {shift;bless [@_ ]}sub DESTROY {my ($code,@args)=@{$_[0]};local $@ if UNSTABLE_DOLLARAT;eval {$code->(@args);1}or do {warn "Execution of finally() block $code resulted in an exception, which " .'*CAN NOT BE PROPAGATED* due to fundamental limitations of Perl. ' .'Your program will continue as if this event never took place. ' ."Original exception text follows:\n\n" .(defined $@ ? $@ : '$@ left undefined...')."\n" }}}__PACKAGE__
TRY_TINY
s/^ //mg for values %fatpacked;
my $class = 'FatPacked::'.(0+\%fatpacked);
no strict 'refs';
*{"${class}::files"} = sub { keys %{$_[0]} };
if ($] < 5.008) {
*{"${class}::INC"} = sub {
if (my $fat = $_[0]{$_[1]}) {
my $pos = 0;
my $last = length $fat;
return (sub {
return 0 if $pos == $last;
my $next = (1 + index $fat, "\n", $pos) || $last;
$_ .= substr $fat, $pos, $next - $pos;
$pos = $next;
return 1;
});
}
};
}
else {
*{"${class}::INC"} = sub {
if (my $fat = $_[0]{$_[1]}) {
open my $fh, '<', \$fat
or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
return $fh;
}
return;
};
}
unshift @INC, bless \%fatpacked, $class;
} # END OF FATPACK CODE
#
# This is development version of the check_raid.pl plugin
# If you are running this file directly, you are doing it wrong
#
# See installation notes:
# https://github.com/glensc/nagios-plugin-check_raid#installing
#
use Monitoring::Plugin 0.37;
use App::Monitoring::Plugin::CheckRaid;
use App::Monitoring::Plugin::CheckRaid::Sudoers;
use App::Monitoring::Plugin::CheckRaid::Plugin;
use App::Monitoring::Plugin::CheckRaid::Utils;
use warnings;
use strict;
my $PROGNAME = 'check_raid';
my $VERSION = q/4.0.5/;
my $URL = 'https://github.com/glensc/nagios-plugin-check_raid';
my $BUGS_URL = 'https://github.com/glensc/nagios-plugin-check_raid#reporting-bugs';
my $mp = Monitoring::Plugin->new(
usage =>
"Usage: %s [-h] [-V] [-S] [list of devices to ignore]",
version => $VERSION,
blurb => join($/,
"This plugin checks all RAID volumes (hardware and software) that can be identified",
"",
"Homepage: $URL",
"Reporting Bugs: $BUGS_URL",
),
plugin => $PROGNAME,
shortname => $PROGNAME,
);
$mp->add_arg(
spec => 'sudoers|S',
help => 'Setup sudo rules',
);
$mp->add_arg(
spec => 'warnonly|W',
help => 'Treat CRITICAL errors as WARNING',
);
$mp->add_arg(
spec => 'debug|d',
help => 'debug mode, or dry-run for sudoers',
);
$mp->add_arg(
spec => 'list_plugins|list-plugins|l',
help => 'Lists active plugins',
);
$mp->add_arg(
spec => 'plugin|p=s@',
help => 'Force the use of selected plugins, comma separated',
);
$mp->add_arg(
spec => 'plugin-option=s@',
help => "Specify extra option for specific plugin.\n" .
'
Plugin options (key=>value pairs) passed as "options" key to each plugin constructor.
The options are global, not plugin specific, but it\'s recommended to prefix option with plugin name.
The convention is to have PLUGIN_NAME-OPTION_NAME=OPTION_VALUE syntax to namespace each plugin option.
For example "--plugin-option=hp_msa-serial=/dev/ttyS2"
would define option "serial" for "hp_msa" plugin with value "/dev/ttyS2".
'
);
$mp->add_arg(
spec => 'noraid=s',
help => 'Return STATE if no RAID volumes are found. Defaults to UNKNOWN',
);
$mp->add_arg(
spec => 'resync=s',
help => 'Return STATE if RAID is in resync state. Defaults to WARNING',
);
$mp->add_arg(
spec => 'check=s',
help => 'Return STATE if RAID is in check state. Defaults to OK',
);
$mp->add_arg(
spec => 'cache-fail=s',
help => 'Set status as STATE if Write Cache is present but disabled. Defaults to WARNING',
);
$mp->add_arg(
spec => 'bbulearn=s',
help => 'Return STATE if Backup Battery Unit (BBU) learning cycle is in progress. Defaults to WARNING',
);
$mp->add_arg(
spec => 'bbu-monitoring',
help => 'Enable experimental monitoring of the BBU status',
);
$mp->add_arg(
spec => 'warnonly|W',
help => 'Treat CRITICAL errors as WARNING',
);
$mp->getopts;
if (@ARGV) {
@App::Monitoring::Plugin::CheckRaid::Utils::ignore = @ARGV;
}
my (%ERRORS) = (OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3);
my %plugin_options;
if ($mp->opts->warnonly) {
App::Monitoring::Plugin::CheckRaid::Plugin->set_critical_as_warning;
}
if ($mp->opts->get('bbu-monitoring')) {
$plugin_options{options}{bbu_monitoring} = 1;
}
# setup state flags
my %state_flags = (
'resync' => 'resync_status',
'check' => 'check_status',
'noraid' => 'noraid_status',
'bbulearn' => 'bbulearn_status',
'cache-fail' => 'cache_fail_status',
);
while (my($opt, $key) = each %state_flags) {
if (my $value = $mp->opts->get($opt)) {
unless (exists $ERRORS{$value}) {
print "Invalid value: '$value' for --$opt\n";
exit $ERRORS{UNKNOWN};
}
$plugin_options{options}{$key} = $ERRORS{$value};
}
}
# enable only specified plugins
if (my $plugins = $mp->opts->plugin) {
# split, as each value can contain commas
$plugin_options{enable_plugins} = [ map { split(/,/, $_) } @$plugins ];
}
if (my $opts = $mp->opts->get('plugin-option')) {
foreach my $o (@$opts) {
my($k, $v) = split(/=/, $o, 2);
$plugin_options{options}{$k} = $v;
}
}
my $mc = App::Monitoring::Plugin::CheckRaid->new(%plugin_options);
$App::Monitoring::Plugin::CheckRaid::Utils::debug = $mp->opts->debug;
if ($mp->opts->debug) {
print "$PROGNAME $VERSION\n";
print "Visit <$BUGS_URL> how to report bugs\n\n",
}
my @plugins = $mc->active_plugins;
if (!@plugins) {
$mp->plugin_exit($plugin_options{options}{noraid_status}, "No active plugins (No RAID found)");
}
if ($mp->opts->sudoers) {
sudoers($mp->opts->debug, @plugins);
$mp->plugin_exit(OK, "sudoers updated");
}
# print active plugins
if ($mp->opts->list_plugins) {
foreach my $p (@plugins) {
print $p->{name}, "\n";
}
my $count = @plugins;
warn "$count active plugins\n";
exit $ERRORS{OK};
}
my $message = '';
my $status = $ERRORS{OK};
# perform check of each active plugin
foreach my $plugin (@plugins) {
# skip if no check method (not standalone checker)
next unless $plugin->can('check');
# perform the check
$plugin->check;
my $pn = $plugin->{name};
# collect results
unless (defined $plugin->status) {
$status = $ERRORS{UNKNOWN} if $ERRORS{UNKNOWN} > $status;
$message .= '; ' if $message;
$message .= "$pn:[Plugin error]";
next;
}
if ($plugin->message or $plugin->{options}{noraid_status} == $ERRORS{UNKNOWN}) {
$status = $plugin->status if $plugin->status > $status;
} else {
$status = $plugin->{options}{noraid_status} if $plugin->{options}{noraid_status} > $status;
}
$message .= '; ' if $message;
$message .= "$pn:[".$plugin->message."]";
$message .= ' | ' . $plugin->perfdata if $plugin->perfdata;
$message .= "\n" . $plugin->longoutput if $plugin->longoutput;
}
if ($message) {
if ($status == $ERRORS{OK}) {
print "OK: ";
} elsif ($status == $ERRORS{WARNING}) {
print "WARNING: ";
} elsif ($status == $ERRORS{CRITICAL}) {
print "CRITICAL: ";
} else {
print "UNKNOWN: ";
}
print "$message\n";
} elsif ($plugin::options{noraid_status} != $ERRORS{UNKNOWN}) {
$status = $plugin::options{noraid_status};
print "No RAID configuration found\n";
} else {
$status = $ERRORS{UNKNOWN};
print "No RAID configuration found (tried: ", join(', ', @plugins), ")\n";
}
exit $status;