You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
5.4 KiB
175 lines
5.4 KiB
#!/usr/bin/perl -w
|
|
|
|
use strict;
|
|
use warnings;
|
|
use JSON;
|
|
use File::Which;
|
|
use Getopt::Long;
|
|
|
|
my $json = {};
|
|
my $pool = undef;
|
|
my $dataset = undef;
|
|
my $sanoidmon = undef;
|
|
my $stats = undef;
|
|
my $pretty = 0;
|
|
|
|
GetOptions(
|
|
"zpool|pool=s" => \$pool,
|
|
"dataset=s" => \$dataset,
|
|
"sanoid=s" => \$sanoidmon,
|
|
"stats=s" => \$stats,
|
|
"pretty" => \$pretty
|
|
);
|
|
|
|
my $zpool = which('zpool');
|
|
my $zfs = which('zfs');
|
|
my $sanoid = which('sanoid');
|
|
|
|
if (not $zpool or not $zfs){
|
|
print 'ZBX_NOTSUPPOTED';
|
|
exit 0;
|
|
}
|
|
if (defined $sanoidmon and not $sanoid){
|
|
die 'ZBX_NOTSUPPOTED';
|
|
}
|
|
if (defined $sanoidmon and not grep { $_ eq $sanoidmon } qw(snapshot capacity health)){
|
|
die 'ZBX_NOTSUPPOTED';
|
|
}
|
|
|
|
if (not $pool and not $dataset and not $sanoidmon and not $stats){
|
|
print <<_EOF;
|
|
Usage:
|
|
$0 [--zpool=<name>|--dataset=<fs zvol or snap>|--sanoid=<snapshot|capacity|health>]
|
|
_EOF
|
|
exit 1;
|
|
}
|
|
|
|
# Value map. For Zabbix, we want 0 instead of none
|
|
# We also prefer on/off represented as 1/0 as it's more efficient
|
|
my $map = {
|
|
18446744073709551615 => 0, # See https://github.com/zfsonlinux/zfs/issues/9306
|
|
none => 0,
|
|
on => 1,
|
|
off => 0
|
|
};
|
|
|
|
if ($pool){
|
|
foreach (qx($zpool get all $pool -p -H)){
|
|
chomp;
|
|
my @parse = split /\t+/, $_;
|
|
$json->{$parse[1]} = (defined $map->{$parse[2]}) ? $map->{$parse[2]} : $parse[2];
|
|
$json->{errors} = get_zpool_errors($pool);
|
|
$json->{stats} = get_zpool_stats($pool);
|
|
}
|
|
} elsif ($dataset){
|
|
# Convert %40 back to @ (we send them as %40 in the discovery because @ is not allowed in item keys
|
|
$dataset =~ s/%40/\@/g;
|
|
foreach (qx($zfs get all $dataset -p -H)){
|
|
chomp;
|
|
my @parse = split /\t+/, $_;
|
|
$json->{$parse[1]} = (defined $map->{$parse[2]}) ? $map->{$parse[2]} : $parse[2];
|
|
if ($parse[1] =~ m/compressratio$/){
|
|
# Remove trailing x for compressratio and refcompressratio as before 0.8.0 it can be like 1.23x
|
|
$json->{$parse[1]} =~ s/x$//;
|
|
}
|
|
}
|
|
} elsif ($sanoidmon){
|
|
print qx($sanoid --monitor-$sanoidmon);
|
|
exit $?;
|
|
} elsif ($stats){
|
|
if (not -e '/proc/spl/kstat/zfs/' . $stats){
|
|
print 'ZBX_NOTSUPPORTED';
|
|
exit 0;
|
|
}
|
|
open STATS, '</proc/spl/kstat/zfs/' . $stats;
|
|
while (<STATS>){
|
|
next unless (m/^(\w+)\s+4\s+(\d+)$/);
|
|
$json->{$1} = $2;
|
|
}
|
|
}
|
|
|
|
print to_json($json, { pretty => $pretty }) . "\n";
|
|
exit 0;
|
|
|
|
sub get_zpool_errors {
|
|
my $pool = shift;
|
|
my $errors = {
|
|
read_errors => 0,
|
|
write_errors => 0,
|
|
cksum_errors => 0
|
|
};
|
|
my $i = 0;
|
|
my $index = {};
|
|
foreach my $line (qx($zpool status $pool)){
|
|
# Output looks like
|
|
# pool: rpool
|
|
# state: ONLINE
|
|
# status: One or more devices has experienced an unrecoverable error. An
|
|
# attempt was made to correct the error. Applications are unaffected.
|
|
# action: Determine if the device needs to be replaced, and clear the errors
|
|
# using 'zpool clear' or replace the device with 'zpool replace'.
|
|
# see: http://zfsonlinux.org/msg/ZFS-8000-9P
|
|
# scan: scrub repaired 0B in 0h5m with 0 errors on Tue May 29 10:04:31 2018
|
|
# config:
|
|
#
|
|
# NAME STATE READ WRITE CKSUM
|
|
# rpool ONLINE 0 0 0
|
|
# mirror-0 ONLINE 0 0 0
|
|
# sda2 ONLINE 0 0 0
|
|
# sdb2 ONLINE 0 0 474
|
|
#
|
|
# errors: No known data errors
|
|
|
|
# We want to save status, action, scan and errors
|
|
if ($line =~ m/^\s*(scan|action|status|errors):\s+(\w+.*)/){
|
|
$errors->{$1} = $2;
|
|
$index->{$i} = $1;
|
|
} elsif ($line !~ /:/ and defined $index->{$i-1}){
|
|
# Here, we reconstitute multiline values (like status and action)
|
|
chomp($line);
|
|
$line =~ s/\s+/ /g;
|
|
$errors->{$index->{$i-1}} .= $line;
|
|
} elsif ($line =~ m/\s+[a-zA-Z0-9_\-]+\s+[A-Z]+\s+(?<read>\d+(\.\d+)?)(?<read_suffix>[KMT])?\s+(?<write>\d+(\.\d+)?)(?<write_suffix>[KMT])?\s+(?<cksum>\d+(\.\d+)?)(?<cksum_suffix>[KMT])?/){
|
|
# And here, we count the number of read, write and checksum errors
|
|
# Note that on ZoL 0.8.0 we could use zpool status -p to get rid of the suffixes
|
|
# But -p is not supported on 0.7 and earlier, so, we just convert them manually
|
|
$errors->{read_errors} += convert_suffix($+{'read'},$+{'read_suffix'});
|
|
$errors->{write_errors} += convert_suffix($+{'write'},$+{'write_suffix'});
|
|
$errors->{cksum_errors} += convert_suffix($+{'write'},$+{'write_suffix'});
|
|
}
|
|
$i++;
|
|
}
|
|
# Ensure evey item returns something
|
|
$errors->{$_} ||= '' foreach (qw(scan action status errors));
|
|
return $errors;
|
|
}
|
|
|
|
# Error counter can be suffixed. Apply this suffix to get raw error numbers
|
|
sub convert_suffix {
|
|
my $val = shift;
|
|
my $suf = shift;
|
|
if (!$suf){
|
|
return $val;
|
|
} elsif ($suf eq 'K'){
|
|
$val *= 1000;
|
|
} elsif ($suf eq 'M') {
|
|
$val *= 1000000;
|
|
} elsif ($suf eq 'T') {
|
|
$val *= 1000000000;
|
|
}
|
|
return $val;
|
|
}
|
|
|
|
sub get_zpool_stats {
|
|
my $pool = shift;
|
|
my $stats = {};
|
|
return $stats unless (-e "/proc/spl/kstat/zfs/$pool/io");
|
|
open STAT, "</proc/spl/kstat/zfs/$pool/io";
|
|
while (<STAT>){
|
|
if (m/^(?<nread>\d+)\s+(?<nwritten>\d+)\s+(?<reads>\d+)\s+(?<writes>\d+)\s+(?<wtime>\d+)\s+(?<wlentime>\d+)\s+(?<wupdate>\d+)\s+(?<rtime>\d+)\s+(?<rlentime>\d+)\s+(?<rupdate>\d+)\s+(?<wcnt>\d+)\s+(?<rcnt>\d+)/){
|
|
$stats->{$_} = $+{$_} foreach (keys %+);
|
|
}
|
|
}
|
|
close STAT;
|
|
return $stats;
|
|
}
|
|
|