diff --git a/zabbix_conf/zfs.conf b/zabbix_conf/zfs.conf index 2dcf769..ffa3836 100644 --- a/zabbix_conf/zfs.conf +++ b/zabbix_conf/zfs.conf @@ -1,13 +1,11 @@ # Discover ZFS zpools # $1 not used for now -UserParameter=vfs.zfs.zpool.discovery[*],/var/lib/zabbix/bin/disco_zfs - -# Type: Agent or Agent (active) -# Key: vfs.zfs.zpool[pool,item] where pool is the name of the zpool to monitor -# item can be one of size, alloc, frag, cap, dedup, health -UserParameter=vfs.zfs.zpool[*],/var/lib/zabbix/bin/check_zfs --zpool=$1 --what=$2 +UserParameter=vfs.zfs.discovery[*],/var/lib/zabbix/bin/disco_zfs --$1 # Type: Agent or Agent (active) # You can also get all the info about a zpool at once, in JSON -UserParameter=vfs.zfs.zpool.all[*],/var/lib/zabbix/bin/check_zfs --zpool=$1 +UserParameter=vfs.zfs.zpool.info[*],/var/lib/zabbix/bin/check_zfs --zpool=$1 +# Type: Agent or Agent (active) +# FS, Zvol or Snap info in JSON +UserParameter=vfs.zfs.dataset.info[*],/var/lib/zabbix/bin/check_zfs --dataset=$1 diff --git a/zabbix_scripts/check_zfs b/zabbix_scripts/check_zfs index adc3c8a..cac4140 100644 --- a/zabbix_scripts/check_zfs +++ b/zabbix_scripts/check_zfs @@ -6,52 +6,70 @@ use JSON; use File::Which; use Getopt::Long; -my $json = {}; -my $pool = undef; -my $what = undef; -my $pretty = 0; +my $json = {}; +my $pool = undef; +my $dataset = undef; +my $sanoidsnap = undef; +my $pretty = 0; GetOptions( "zpool|pool=s" => \$pool, - "what=s" => \$what, - "pretty" => \$pretty + "dataset=s" => \$dataset, + "sanoidsnap" => \$sanoidsnap, + "pretty" => \$pretty ); -my $zpool = which('zpool'); +my $zpool = which('zpool'); +my $zfs = which('zfs'); +my $sanoid = which('sanoid'); -if ($what and !$pool){ +if (not $zpool or not $zfs){ + print 'ZBX_NOTSUPPOTED'; + exit 0; +} +if ($sanoidsnap and not $sanoid){ + die 'ZBX_NOTSUPPOTED'; +} + +if (not $pool and not $dataset and not $sanoidsnap){ print <<_EOF; Usage: -$0 [--zpool=] [--what=] - - is an optional zpool name. If specified, will only output info for this zpool. - is one of size, alloc, frag, cap, dedup, health and if specified, will only output the corresponding value -If --what is specified then --zpool is mandatory. -The default (with no option) is to output all the info of all the zpool in a JSON format +$0 [--zpool=|--dataset=|--sanoidsnap] _EOF exit 1; } -if ($zpool){ - my $cmd = "$zpool list -p -H" . ( ($pool) ? " $pool" : ""); - foreach (qx($cmd)){ - #NAME SIZE ALLOC FREE EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT - #rpool 464G 7.49G 457G - 2% 1% 1.00x ONLINE - - if (m/^(?\w+)\s+(?\d+)\s+(?\d+)\s+(?\d+)\s+.+\s+(?\d+(\.\d+)?)\s+(?\d+(\.\d+)?)\s+(?\d+(\.\d+)?)\s+(?\w+)/){ - $json->{$+{pool}}->{$_} = $+{$_} foreach (grep { $_ ne 'pool' } keys %+); - } - $json->{$+{pool}}->{errors} = get_zpool_errors($+{pool}); - $json->{$+{pool}}->{stats} = get_zpool_stats($+{pool}); +# 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]; + } +} elsif ($sanoidsnap){ + print qx($sanoid --monitor-snapshot); + exit 0; } -if ($what){ - print ((defined $json->{$pool}->{$what}) ? $json->{$pool}->{$what} : 'ZBX_NOTSUPPORTED'); -} elsif ($pool){ - print ((defined $json->{$pool}) ? to_json($json->{$pool}, { pretty => $pretty }) : 'ZBX_NOTSUPPORTED'); -} else { - print to_json($json, { pretty => $pretty }); -} -print "\n"; + +print to_json($json, { pretty => $pretty }) . "\n"; exit 0; sub get_zpool_errors { @@ -63,7 +81,7 @@ sub get_zpool_errors { }; my $i = 0; my $index = {}; - foreach my $line (qx($zpool status $pool)){ + foreach my $line (qx($zpool status -p $pool)){ # Output looks like # pool: rpool # state: ONLINE @@ -92,11 +110,11 @@ sub get_zpool_errors { chomp($line); $line =~ s/\s+/ /g; $errors->{$index->{$i-1}} .= $line; - } elsif ($line =~ m/\s+[a-zA-Z0-9_\-]+\s+[A-Z]+\s+(?\d+(\.\d+)?)(?[KMT])?\s+(?\d+(\.\d+)?)(?[KMT])?\s+(?\d+(\.\d+)?)(?[KMT])?/){ + } elsif ($line =~ m/\s+[a-zA-Z0-9_\-]+\s+[A-Z]+\s+(?\d+(\.\d+)?)\s+(?\d+(\.\d+)?)\s+(?\d+(\.\d+)?)/){ # And here, we count the number of read, write and checksum errors - $errors->{read_errors} += convert_suffix($+{'read'},$+{'read_suffix'}); - $errors->{write_errors} += convert_suffix($+{'write'},$+{'write_suffix'}); - $errors->{cksum_errors} += convert_suffix($+{'cksum'},$+{'cksum_suffix'}); + $errors->{read_errors} += $+{'read'}; + $errors->{write_errors} += $+{'write'}; + $errors->{cksum_errors} += $+{'cksum'}; } $i++; } @@ -105,22 +123,6 @@ sub get_zpool_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 = {}; diff --git a/zabbix_scripts/disco_zfs b/zabbix_scripts/disco_zfs index 54d3ff7..3685b10 100644 --- a/zabbix_scripts/disco_zfs +++ b/zabbix_scripts/disco_zfs @@ -4,15 +4,70 @@ use strict; use warnings; use JSON; use File::Which; +use Getopt::Long; my $json; @{$json->{data}} = (); -my $zpool = which('zpool'); +my $zpool = which('zpool'); +my $zfs = which('zfs'); +my $sanoid = which('sanoid'); -if ($zpool){ +if (not $zpool or not $zfs){ + print 'ZBX_NOTSUPPOTED'; + exit(0); +} + +my $pools = 1; +my $fs = 0; +my $zvol = 0; +my $snap = 0; +my $sanoidsnap = 0; +my $pretty = 0; + +GetOptions( + "pools" => \$pools, + "fs|filesystems" => \$fs, + "zvols|volumes" => \$zvol, + "snapshots" => \$snap, + "sanoidsnap" => \$sanoidsnap, + "pretty" => \$pretty +); + +if ($fs or $zvol or $snap or $sanoidsnap){ + $pools = 0; +} +if ($pools + $fs + $zvol + $snap + $sanoidsnap != 1){ + die "One and only one type of discovery should be provided\n"; +} +if ($sanoidsnap and not $sanoid){ + print to_json($json); + exit 0; +} + +if ($pools){ foreach (qx($zpool list -H -o name)){ chomp; push @{$json->{data}}, { '{#ZPOOL}' => $_ }; } +} elsif ($fs){ + foreach (qx($zfs list -H -o name -t filesystem)){ + chomp; + push @{$json->{data}}, { '{#ZFS_FS}' => $_ }; + } +} elsif ($zvol){ + foreach (qx($zfs list -H -o name -t volume)){ + chomp; + push @{$json->{data}}, { '{#ZFS_ZVOL}' => $_ }; + } +} elsif ($snap){ + foreach (qx($zfs list -H -o name -t snap)){ + chomp; + # Remove @ as they are not allowed in item key names + # They will be converted back to @ by check_zfs script + $_ =~ s/\@/%40/g; + push @{$json->{data}}, { '{#ZFS_SNAP}' => $_ }; + } +} elsif ($sanoidsnap){ + push @{$json->{data}}, { '{#ZFS_SANOID}' => 1 }; } -print to_json($json); +print to_json($json, { pretty => $pretty });