#!/usr/bin/perl # 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; # whether the query is for sudo rules my $sudo = shift || 0; 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($sudo); 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 parse_bytes { my ($this, $size) = @_; if ($size =~ s/\sT//) { return int($size) * T; } if ($size =~ s/\sG//) { return int($size) * G; } if ($size =~ s/\sM//) { return int($size) * M; } if ($size =~ s/\sK//) { return int($size) * K; } return int($size); } 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 active { my ($this, $sudo) = @_; # return if parent said NO my $res = $this->SUPER::active(@_); return $res unless $res; # check if there really are any devices my $c = $this->parse; return !!@$c; } 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; # cache for single run if (!defined($this->{parsed})) { $this->{parsed} = $this->_parse; } return $this->{parsed}; } 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) { # 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}"; # 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 () { 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/ssacli 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 is of format: # [controller all|slot=#|wwn=#|chassisname="AAA"|serialnumber=#|chassisserialnumber=#|ctrlpath=#:# ] # [array all|] # [physicaldrive all|allunassigned|[#:]#:#|[#:]#:#-[#:]#:#] # [logicaldrive all|#] # [enclosure all|#:#|serialnumber=#|chassisname=#] # [licensekey all|] # 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+)\))?$/i) { $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; my @spare_options = (); @spare_options = split(/\,/, $this->{options}{mdstat_spare_count}) if (exists $this->{options}{mdstat_spare_count}); 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}}; # spare disks my @sd = map { $_->{dev} } grep { $_->{flags} =~ /S/ } @{$md{disks}}; my $spare_count = 0; OPTION_LOOP: { foreach my $i (0 .. $#spare_options) { my ($disk, $value) = split(/:/, $spare_options[$i]); for(@md) { if ($md{dev} eq $disk) { $spare_count = $value; splice(@spare_options, $i, 1); last OPTION_LOOP; } } } } # 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}"; } elsif (@sd < $spare_count) { $this->warning; $s .= "Array ".$md{dev}." should have ".$spare_count." spares, but has only ".(0+@sd)." spares"; } else { $s .= "$md{status}"; } push(@status, $s); } if (scalar @spare_options > 0) { $this->critical; foreach (@spare_options) { my ($disk, $value) = split(/:/, $_); my $s = "$disk is defined in spare_count option but could not be found!"; 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 () { # / 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 () { 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 () { # 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); } sub get_controller { my $this = shift; # controller ID may be given on the command line my $id = $this->{options}{'mpt-id'}; if (!$id) { # get controller from mpt-status -p my $fh = $this->cmd('get_controller_no'); 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', 'info', '-o', 'blk'], 'mvcli vd' => ['-|', '@CMD', 'info', '-o', 'vd'], 'mvcli smart' => ['-|', '@CMD', 'smart', '-p', '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" } 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_vd { my $this = shift; my (@vd, %vd); my ($name, $value); my $fh = $this->cmd('mvcli vd'); while (<$fh>) { chomp; if (/^$/ || /----+/ || /SG driver version/ || /Virtual Disk Information/ ) { next; } unless (($name, $value) = /^(.+):\s+(.+)$/) { warn "UNPARSED: [$_]"; next; } if ($name eq 'id') { # id is first item, so push previous item to list if (%vd) { push(@vd, { %vd }); %vd = (); } } $vd{$name} = $value; } close $fh; if (%vd) { push(@vd, { %vd }); } return wantarray ? @vd : \@vd; } 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, $status) = / ([\dA-F]{2})\s+ # id (.*?)\s+ # name (\d+)\s+ # current (\d+)\s+ # worst (\d+)\s+ # treshold ([\dA-F]{12}) # raw (?:\s+(\w+))? # status /x) { my %attr = (); $attr{id} = $id; $attr{name} = $name; $attr{current} = int($current); $attr{worst} = int($worst); $attr{treshold} = int($treshold); $attr{raw} = $raw; $attr{status} = $status || undef; $attrs{$id} = { %attr }; } else { # warn "[$_]\n"; } } $smart{$pd} = { %attrs }; } return \%smart; } sub parse { my $this = shift; my $blk = $this->parse_blk; my $vd = $this->parse_vd; my $smart = $this->parse_smart($blk); return { blk => $blk, vd => $vd, smart => $smart, }; } sub check { my $this = shift; my @status; my $c = $this->parse; foreach my $vd (@{$c->{vd}}) { my $size = $this->format_bytes($this->parse_bytes($vd->{size})); if ($vd->{status} ne 'functional') { $this->critical; } push(@status, "VD($vd->{name} $vd->{'RAID mode'} $size): $vd->{status}"); } return unless @status; # denote this plugin as ran ok $this->ok; $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/ssacli.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_MONITORING_PLUGIN_CHECKRAID_PLUGINS_SSACLI'; package App::Monitoring::Plugin::CheckRaid::Plugins::ssacli; # 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_SSACLI $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 # @returns true if file was updated 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 0; } 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, icinga, sensu", "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 0; } 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"; return 1; } warn "$sudoers file not changed.\n"; unlink($new); return 0; } # 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 $!; # prefer -n to skip password prompt push(@sudo, '-n') if /-n/; # ..if not supported, add -A as well 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.51';sub new {return bless defined $_[1]? {%{$_[1]}}: {},ref $_[0]|| $_[0]}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;use B 'perlstring';$Class::Accessor::Fast::VERSION='0.51';sub make_accessor {my ($class,$field)=@_;eval sprintf q{ sub { return $_[0]{%s} if scalar(@_) == 1; return $_[0]{%s} = scalar(@_) == 2 ? $_[1] : [@_[1..$#_]]; } },map {perlstring($_)}$field,$field}sub make_ro_accessor {my($class,$field)=@_;eval sprintf q{ sub { return $_[0]{%s} if @_ == 1; my $caller = caller; $_[0]->_croak(sprintf "'$caller' cannot alter the value of '%%s' on objects of class '%%s'", %s, %s); } },map {perlstring($_)}$field,$field,$class}sub make_wo_accessor {my($class,$field)=@_;eval sprintf q{ sub { if (@_ == 1) { my $caller = caller; $_[0]->_croak(sprintf "'$caller' cannot access the value of '%%s' on objects of class '%%s'", %s, %s); } else { return $_[0]{%s} = $_[1] if @_ == 2; return (shift)->{%s} = \@_; } } },map {perlstring($_)}$field,$class,$field,$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;use B 'perlstring';$Class::Accessor::Faster::VERSION='0.51';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);eval sprintf q{ sub { return $_[0][%d] if scalar(@_) == 1; return $_[0][%d] = scalar(@_) == 2 ? $_[1] : [@_[1..$#_]]; } },$n,$n}sub make_ro_accessor {my($class,$field)=@_;my$n=$class->_slot($field);eval sprintf q{ sub { return $_[0][%d] if @_ == 1; my $caller = caller; $_[0]->_croak(sprintf "'$caller' cannot alter the value of '%%s' on objects of class '%%s'", %s, %s); } },$n,map(perlstring($_),$field,$class)}sub make_wo_accessor {my($class,$field)=@_;my$n=$class->_slot($field);eval sprintf q{ sub { if (@_ == 1) { my $caller = caller; $_[0]->_croak(sprintf "'$caller' cannot access the value of '%%s' on objects of class '%%s'", %s, %s); } else { return $_[0][%d] = $_[1] if @_ == 2; return (shift)->[%d] = \@_; } } },map(perlstring($_),$field,$class),$n,$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=;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(){/^\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=){$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.016";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.40";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.40";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}";if (defined$message && $message ne ''){$output .= " - " unless$message =~ /^\s*\n/mxs;$output .= $message}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$boolean=($spec =~ m/!/);my (@short,@long);for (split /\|/,$opts){if (length $_==1){push@short,"-$_"}else {push@long,$boolean ? "--[no-]$_" : "--$_"}}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 ($@);defined$Config or $self->_die(Monitoring::Plugin::Config->errstr);$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;my%params=(spec=>1,help=>1,default=>0,required=>0,label=>0,);if (exists$params{$_[0]}&& @_ % 2==0){%args=validate(@_,\%params)}else {my@order=qw(spec help default required label);@args{@order}=validate_pos(@_,@params{@order})}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[-_]//i;$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$value=$self->value;if ($value eq ''){$value='U'}my$out=sprintf "%s=%s%s;%s;%s;%s;%s",$label,$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.29';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.29';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.29';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 {"'$_'"}sort@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.29';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.29';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.29';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.30';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')}}_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;_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;_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.9/; 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->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"; print "Please include output of **ALL** commands in bugreport\n\n"; } if ($mp->opts->sudoers) { my $res = sudoers($mp->opts->debug, $mc->active_plugins(1)); $mp->plugin_exit(OK, $res ? "sudoers updated" : "sudoers not updated"); } my @plugins = $mc->active_plugins; if (!@plugins) { $mp->plugin_exit($plugin_options{options}{noraid_status}, "No active plugins (No RAID found)"); } # 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;