Skip to content

Instantly share code, notes, and snippets.

@gonter
Last active August 29, 2015 14:24
Show Gist options
  • Save gonter/0deb972e4684bddb22e2 to your computer and use it in GitHub Desktop.
Save gonter/0deb972e4684bddb22e2 to your computer and use it in GitHub Desktop.
arclog
out-nagios.txt
out-data.csv
x
orko_arcconf_device_logs.*

arclog parser

check out from github:

git clone https://gist.github.com/0deb972e4684bddb22e2.git

Usage:

./arclog.pl x/orko_arcconf_device_logs.text

produces two files:

  • out-data.csv contains collected disk data
  • out-nagios.txt contains a summary which can be used for a Nagios/Icinga check

csv --TAB out-data.csv | less -S

cat out-nagios.txt

possible improvements

  • allow calling ssh instead of interpreting a text file which is the result of a dsh command: This could allow for better, more focused error handling.

see also

  • https://github.com/kumy/Parse-Arcconf Perl module Parse::Arcconf parses the output of "arcconf" utility to allow programmatic access to the RAID configuration information provided by the arcconf utility on Adaptec RAID cards.
#!/usr/bin/perl
=head1 NAME
dummy script doing nothing
=cut
use strict;
# use FindBin;
# use lib "$FindBin::Bin";
use Data::Dumper;
$Data::Dumper::Indent= 1;
use Util::Simple_CSV;
use Util::Matrix;
# use Module;
my $x_flag= 0;
my $is_dsh= 1;
# BEGIN module data
my @fld_ids= qw( wwn deviceID serialNumber vendorID productID );
my @fld_diag= qw(critical warnings);
my @fld_errors= qw( mediumErrors smartError numParityErrors hwErrors smartWarning linkFailures abortedCmds );
my @fld= (@fld_ids, @fld_diag, @fld_errors);
# END module data
my %limits=
(
# 'mediumErrors' => { 'type' => 'num', 'warn' => 10000, 'crit' => 20000 },
# 'abortedCmds' => { 'type' => 'num', 'warn' => 10, 'crit' => 20 },
'smartError' => { 'type' => 'bool', 'xwarn' => 'true', 'crit' => 'true' },
);
my @PARS;
my $arg;
while (defined ($arg= shift (@ARGV)))
{
if ($arg eq '--') { push (@PARS, @ARGV); @ARGV= (); }
elsif ($arg =~ /^--(.+)/)
{
my ($opt, $val)= split ('=', $1, 2);
if ($opt eq 'help') { usage(); }
else { usage(); }
}
elsif ($arg =~ /^-(.+)/)
{
foreach my $opt (split ('', $1))
{
if ($opt eq 'h') { usage(); exit (0); }
elsif ($opt eq 'x') { $x_flag= 1; }
else { usage(); }
}
}
else
{
push (@PARS, $arg);
}
}
# print join (' ', __FILE__, __LINE__, 'caller=['. caller() . ']'), "\n";
my $arclog= new Arcconf::Log('is_dsh' => $is_dsh);
while (defined ($arg= shift (@PARS)))
{
$arclog->parse_file ($arg);
}
# print "arclog: ", Dumper ($arclog);
my ($data, $crit, $warn)= $arclog->convert_to_csv_rows (\%limits);
# print "data: ", Dumper ($data);
# print "crit: ", Dumper ($crit);
# print "warn: ", Dumper ($warn);
my $level= 'OK';
$level= 'WARNING' if (@$warn);
$level= 'CRITICAL' if (@$crit);
print "level=[$level]\n";
my $fnm_nagios= 'out-nagios.txt';
if (defined ($fnm_nagios))
{
open (FO, '>:utf8', $fnm_nagios) or die "...TODO...";
=begin comment
Preparing data for the nagios check
The generalized service check script e.g. for Phaidra uses this
format.
* First line: code plus timestamp
* after that: information that might be helpful for ops
TODO:
The nagios check script (on the nagios machine) currently uses localtime
which triggers alerts during summertime switch because data is then
either too old or too fresh. Thus the nagios check script needs to
pickup the timezone code "Z" to handle gmt times properly.
=end comment
=cut
# my @ts= gmtime(time()); my $tz_info= 'Z';
my @ts= localtime(time()); my $tz_info= '';
printf FO ("%s - %04d%02d%02dT%02d%02d%02d%s\n\n", $level, $ts[5]+1900, $ts[4]+1, $ts[3], $ts[2], $ts[1], $ts[0], $tz_info);
if (@$crit)
{
print FO "\n", scalar(@$crit), " critical errors\n", Dumper($crit);
# print FO join (' ', %$_), "\n" foreach (@$crit);
# Util::Matrix::print (\@fld, $crit, *FO);
}
if (@$warn)
{
print FO "\n", scalar(@$warn), " warnings\n", Dumper ($warn);
# print FO join (' ', %$_), "\n" foreach (@$warn);
# Util::Matrix::print (\@fld, $warn, *FO);
}
close (FO);
}
my $csv_fnm= 'out-data.csv';
my $csv= new Util::Simple_CSV ('separator' => "\t"); # TODO: set CSV attributes as needed
$csv->{'data'}= $data;
$csv->define_columns(@fld);
$csv->save_csv_file('filename' => $csv_fnm);
print "saved to $csv_fnm\n";
exit (0);
sub usage
{
print <<EOX;
usage: $0 [-opts] pars
template ...
options:
-h ... help
-x ... set x flag
-- ... remaining args are parameters
EOX
exit (0);
}
# ----------------------------------------------------------------------------
sub main_function
{
my $fnm= shift;
print "main_function: $fnm\n";
}
package Arcconf::Log;
sub new
{
my $class= shift;
my $obj= {};
bless $obj, $class;
$obj->set (@_);
$obj;
}
sub set
{
my $self= shift;
my %par= @_;
foreach my $par (keys %par)
{
$self->{$par}= $par{$par};
}
}
sub parse_file
{
my $self= shift;
my $fnm= shift;
open (FI, '<:utf8', $fnm) or die;
$self->parse_fh(*FI);
close (FI);
}
sub parse_fh
{
my $self= shift;
my $fh= shift;
my $is_dsh= $self->{'is_dsh'};
my $host_name= 'nohost'; # TODO or get that from $self
my $last_host= '<none>';
my ($host, $ctrl); # pointer to current objects
while (<$fh>)
{
chop;
# print ">> [$_]\n";
if ($is_dsh)
{ # filter out host info
if ($_ =~ s#^([\w\d\-\.]+): ##) { $host_name= $1; }
}
if ($host_name ne $last_host)
{
$last_host= $host_name;
# print __LINE__, " new_host=[$host_name]\n";
$host= $self->{'host'}->{$host_name}= {}; # TODO: or new object of some class
}
if ($_ =~ m#<ControllerLog (.+)>#)
{
my $c_attr= get_attr_list($1);
$ctrl=
{
'attr' => $c_attr,
'drives' => [],
};
# print "new controller: ", main::Dumper($ctrl);
push (@{$host->{'controller'}}, $ctrl);
}
elsif ($_ =~ m#<driveErrorEntry (.+)\s*/>#)
{
my $d_attr= get_attr_list($1);
# print "new drive: ", main::Dumper ($d_attr);
push (@{$ctrl->{'drives'}}, $d_attr);
}
elsif ($_ eq '</ControllerLog>') {} # close tags
elsif ($_ eq '' || $_ eq 'Command completed successfully.') {}
elsif ($_ =~ m#Controllers found: (\d+)#)
{
# TODO: maybe we need that sometime ...
}
else
{
print "unknown line: [$_]\n";
}
}
# TODO: return something
}
sub convert_to_csv_rows
{
my $self= shift;
my $limits= shift;
my @rows;
my @host_names= sort keys %{$self->{'host'}};
# print "host_names: ", join (' ', @host_names), "\n";
my @crit;
my @warn;
my $level= 0;
my @check;
if (defined ($limits))
{
@check= sort keys %$limits;
print "check limits: ", join (' ', @check), "\n";
}
foreach my $host (@host_names)
{
# print "host=[$host]\n";
my $p_host= $self->{'host'}->{$host};
foreach my $p_ctrl (@{$p_host->{'controller'}})
{
my $ctrl_id= $p_ctrl->{'attr'}->{'controllerID'};
# print "ctrl_id=[$ctrl_id]\n";
foreach my $p_drive (@{$p_ctrl->{'drives'}})
{
my %row= %$p_drive;
$row{'host'}= $host;
$row{'ControllerID'}= $ctrl_id;
push (@rows, \%row);
if (defined ($limits))
{
# use either counter or array
my $c_warn= 0;
my $c_crit= 0;
my (@r_warn, @r_crit);
foreach my $an (@check)
{
my $c= $limits->{$an};
my $av= $row{$an};
if ($c->{'type'} eq 'bool')
{
if ($av eq $c->{'crit'}) { push (@crit, \%row); $c_crit++; push (@r_crit, $an); }
elsif ($av eq $c->{'warn'}) { push (@warn, \%row); $c_warn++; push (@r_warn, $an); }
# else that's fine
}
elsif ($c->{'type'} eq 'num')
{
if ($av >= $c->{'crit'}) { push (@crit, \%row); $c_crit++; push (@r_crit, $an); }
elsif ($av >= $c->{'warn'}) { push (@warn, \%row); $c_warn++; push (@r_warn, $an); }
# else that's fine
}
# TOOD: more check types??
}
# $row{'warnings'}= $c_warn;
# $row{'critical'}= $c_crit;
$row{'warnings'}= join (',', @r_warn);
$row{'critical'}= join (',', @r_crit);
}
}
}
}
(\@rows, \@crit, \@warn);
}
=head1 INTERNAL FUNCTIONS
...
=cut
sub set_attr
{
my ($a, $an, $av)= @_;
# print __LINE__, " >>>> an=[$an] av=[$av]\n";
$av =~ s#^\s+##;
$av =~ s#\s+$##;
$a->{$an}= $av;
'';
}
sub get_attr_list
{
my $s= shift;
# print ">>> s=[$s]\n";
my $attr= {};
# $s =~ s#(\w+)="([^"]+)"#{$attr{$1}=$2;''}#ge;
$s =~ s#(\w+)="([^"]+)"#set_attr($attr, $1,$2)#ge;
# print "<<< s=[$s]\n";
# print __LINE__, " attr: ", main::Dumper($attr);
$attr;
}
__END__
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment