diff --git a/snmp/zfs-freebsd b/snmp/zfs-freebsd new file mode 100755 index 0000000..db6f33b --- /dev/null +++ b/snmp/zfs-freebsd @@ -0,0 +1,266 @@ +#!/usr/bin/env perl + +=head1 DESCRIPTION + +This is a SNMP extend for ZFS and FreeBSD for use with LibreNMS. + +For more information, see L. + +=head1 SWITCHES + +=head2 -p + +Pretty print the JSON. + +=head1 SNMPD SETUP EXAMPLES + + extend zfs /etc/snmp/zfs-freebsd + +=cut + +#Copyright (c) 2017, Zane C. Bowers-Hadley +#All rights reserved. +# +#Redistribution and use in source and binary forms, with or without modification, +#are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +#ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +#IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +#INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +#BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +#LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +#OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +#THE POSSIBILITY OF SUCH DAMAGE. + +# Many thanks to Ben Rockwood, Jason J. Hellenthal, and Martin Matuska +# for zfs-stats and figuring out the math for all the stats + +use strict; +use warnings; +use JSON; +use Getopt::Std; + +$Getopt::Std::STANDARD_HELP_VERSION = 1; +sub main::VERSION_MESSAGE { + print "FreeBSD ZFS stats extend 0.0.0\n"; +} + +sub main::HELP_MESSAGE { + +} + +#this will be dumped to json at the end +my %tojson; + +#gets the options +my %opts=(); +getopts('p', \%opts); + +my $sysctls; +my @to_pull=( + 'kstat.zfs', + 'vfs.zfs', + ); +my @sysctls_pull = `/sbin/sysctl -q @to_pull`; +foreach my $stat (@sysctls_pull) { + chomp( $stat ); + my ( $var, $val ) = split(/:/, $stat, 2); + + $val =~ s/^ //; + $sysctls->{$var}=$val; +} + +# does not seem to exist for me, but some of these don't seem to be created till needed +if ( ! defined( $sysctls->{"kstat.zfs.misc.arcstats.recycle_miss"} ) ) { + $sysctls->{"kstat.zfs.misc.arcstats.recycle_miss"}=0; +} + +## +## ARC misc +## +$tojson{deleted}=$sysctls->{"kstat.zfs.misc.arcstats.deleted"}; +$tojson{evict_skip}=$sysctls->{"kstat.zfs.misc.arcstats.evict_skip"}; +$tojson{mutex_skip}=$sysctls->{'kstat.zfs.misc.arcstats.mutex_miss'}; +$tojson{recycle_miss}=$sysctls->{"kstat.zfs.misc.arcstats.recycle_miss"}; + +## +## ARC size +## +my $target_size_percent = $sysctls->{"kstat.zfs.misc.arcstats.c"} / $sysctls->{"kstat.zfs.misc.arcstats.c_max"} * 100; +my $arc_size_percent = $sysctls->{"kstat.zfs.misc.arcstats.size"} / $sysctls->{"kstat.zfs.misc.arcstats.c_max"} * 100; +my $target_size_adaptive_ratio = $sysctls->{"kstat.zfs.misc.arcstats.c"} / $sysctls->{"kstat.zfs.misc.arcstats.c_max"}; +my $min_size_percent = $sysctls->{"kstat.zfs.misc.arcstats.c_min"} / $sysctls->{"kstat.zfs.misc.arcstats.c_max"} * 100; + +$tojson{arc_size}=$sysctls->{"kstat.zfs.misc.arcstats.size"}; +$tojson{target_size_max}=$sysctls->{"kstat.zfs.misc.arcstats.c_max"}; +$tojson{target_size_min}=$sysctls->{"kstat.zfs.misc.arcstats.c_min"}; +$tojson{target_size}=$sysctls->{"kstat.zfs.misc.arcstats.c"}; +$tojson{target_size_per}=$target_size_percent; +$tojson{arc_size_per}=$arc_size_percent; +$tojson{target_size_arat}=$target_size_adaptive_ratio; +$tojson{min_size_per}=$min_size_percent; + +## +## ARC size breakdown +## +my $mfu_size; +my $recently_used_percent; +my $frequently_used_percent; +if ( $sysctls->{"kstat.zfs.misc.arcstats.size"} >= $sysctls->{"kstat.zfs.misc.arcstats.size"} ){ + $mfu_size = $sysctls->{"kstat.zfs.misc.arcstats.size"} - $sysctls->{"kstat.zfs.misc.arcstats.p"}; + $recently_used_percent = $sysctls->{"kstat.zfs.misc.arcstats.p"} / $sysctls->{"kstat.zfs.misc.arcstats.size"} * 100; + $frequently_used_percent = $mfu_size / $sysctls->{"kstat.zfs.misc.arcstats.size"} * 100; +}else{ + $mfu_size = $sysctls->{"kstat.zfs.misc.arcstats.c"} - $sysctls->{"kstat.zfs.misc.arcstats.p"}; + $recently_used_percent = $sysctls->{"kstat.zfs.misc.arcstats.p"} / $sysctls->{"kstat.zfs.misc.arcstats.c"} * 100; + $frequently_used_percent = $mfu_size / $sysctls->{"kstat.zfs.misc.arcstats.c"} * 100; +} + +$tojson{mfu_size}=$mfu_size; +$tojson{p}=$sysctls->{"kstat.zfs.misc.arcstats.p"}; +$tojson{rec_used_per}=$recently_used_percent; +$tojson{freq_used_per}=$frequently_used_percent; + +## +## ARC efficiency +## +my $arc_hits = $sysctls->{"kstat.zfs.misc.arcstats.hits"}; +my $arc_misses = $sysctls->{"kstat.zfs.misc.arcstats.misses"}; +my $demand_data_hits = $sysctls->{"kstat.zfs.misc.arcstats.demand_data_hits"}; +my $demand_data_misses = $sysctls->{"kstat.zfs.misc.arcstats.demand_data_misses"}; +my $demand_metadata_hits = $sysctls->{"kstat.zfs.misc.arcstats.demand_metadata_hits"}; +my $demand_metadata_misses = $sysctls->{"kstat.zfs.misc.arcstats.demand_metadata_misses"}; +my $mfu_ghost_hits = $sysctls->{"kstat.zfs.misc.arcstats.mfu_ghost_hits"}; +my $mfu_hits = $sysctls->{"kstat.zfs.misc.arcstats.mfu_hits"}; +my $mru_ghost_hits = $sysctls->{"kstat.zfs.misc.arcstats.mru_ghost_hits"}; +my $mru_hits = $sysctls->{"kstat.zfs.misc.arcstats.mru_hits"}; +my $prefetch_data_hits = $sysctls->{"kstat.zfs.misc.arcstats.prefetch_data_hits"}; +my $prefetch_data_misses = $sysctls->{"kstat.zfs.misc.arcstats.prefetch_data_misses"}; +my $prefetch_metadata_hits = $sysctls->{"kstat.zfs.misc.arcstats.prefetch_metadata_hits"}; +my $prefetch_metadata_misses = $sysctls->{"kstat.zfs.misc.arcstats.prefetch_metadata_misses"}; + +my $anon_hits = $arc_hits - ($mfu_hits + $mru_hits + $mfu_ghost_hits + $mru_ghost_hits); +my $arc_accesses_total = $arc_hits + $arc_misses; +my $demand_data_total = $demand_data_hits + $demand_data_misses; +my $prefetch_data_total = $prefetch_data_hits + $prefetch_data_misses; +my $real_hits = $mfu_hits + $mru_hits; + +my $cache_hit_percent = $arc_hits / $arc_accesses_total * 100; +my $cache_miss_percent = $arc_misses / $arc_accesses_total * 100; +my $actual_hit_percent = $real_hits / $arc_accesses_total * 100; +my $data_demand_percent = $demand_data_hits / $demand_data_total * 100; + +my $data_prefetch_percent; +if ( $prefetch_data_total != 0 ) { + $data_prefetch_percent = $prefetch_data_hits / $prefetch_data_total * 100; +}else{ + $data_prefetch_percent = 0; +} + +my $anon_hits_percent; +if ( $anon_hits != 0 ) { + $anon_hits_percent = $anon_hits / $arc_hits * 100; +}else{ + $anon_hits_percent=0; +} + +my $mru_percent = $mru_hits / $arc_hits * 100; +my $mfu_percent = $mfu_hits / $arc_hits * 100; +my $mru_ghost_percent = $mru_ghost_hits / $arc_hits * 100; +my $mfu_ghost_percent = $mfu_ghost_hits / $arc_hits * 100; + +my $demand_hits_percent = $demand_data_hits / $arc_hits * 100; +my $prefetch_hits_percent = $prefetch_data_hits / $arc_hits * 100; +my $metadata_hits_percent = $demand_metadata_hits / $arc_hits * 100; +my $prefetch_metadata_hits_percent = $prefetch_metadata_hits / $arc_hits * 100; + +my $demand_misses_percent = $demand_data_misses / $arc_misses * 100; +my $prefetch_misses_percent = $prefetch_data_misses / $arc_misses * 100; +my $metadata_misses_percent = $demand_metadata_misses / $arc_misses * 100; +my $prefetch_metadata_misses_percent = $prefetch_metadata_misses / $arc_misses * 100; + +# ARC misc. efficient stats +$tojson{arc_hits}=$arc_hits; +$tojson{arc_misses}=$arc_misses; +$tojson{demand_data_hits}=$demand_data_hits; +$tojson{demand_data_misses}=$demand_data_misses; +$tojson{demand_meta_hits}=$demand_metadata_hits; +$tojson{demand_meta_misses}=$demand_metadata_misses; +$tojson{mfu_ghost_hits}=$mfu_ghost_hits; +$tojson{mfu_hits}=$mfu_hits; +$tojson{mru_ghost_hits}=$mru_ghost_hits; +$tojson{mru_hits}=$mru_hits; +$tojson{pre_data_hits}=$prefetch_data_hits; +$tojson{pre_data_misses}=$prefetch_data_misses; +$tojson{pre_meta_hits}=$prefetch_metadata_hits; +$tojson{pre_meta_misses}=$prefetch_metadata_misses; +$tojson{anon_hits}=$anon_hits; +$tojson{arc_accesses_total}=$arc_accesses_total; +$tojson{demand_data_total}=$demand_data_total; +$tojson{pre_data_total}=$prefetch_data_total; +$tojson{real_hits}=$real_hits; + +# ARC efficient percents +$tojson{cache_hits_per}=$cache_hit_percent; +$tojson{cache_miss_per}=$cache_miss_percent; +$tojson{actual_hit_per}=$actual_hit_percent; +$tojson{data_demand_per}=$data_demand_percent; +$tojson{data_pre_per}=$data_prefetch_percent; +$tojson{anon_hits_per}=$anon_hits_percent; +$tojson{mru_per}=$mru_percent; +$tojson{mfu_per}=$mfu_percent; +$tojson{mru_ghost_per}=$mru_ghost_percent; +$tojson{mfu_ghost_per}=$mfu_ghost_percent; +$tojson{demand_hits_per}=$demand_hits_percent; +$tojson{pre_hits_per}=$prefetch_hits_percent; +$tojson{meta_hits_per}=$metadata_hits_percent; +$tojson{pre_meta_hits_per}=$prefetch_metadata_hits_percent; +$tojson{demand_misses_per}=$demand_misses_percent; +$tojson{pre_misses_per}=$prefetch_misses_percent; +$tojson{meta_misses_per}=$metadata_misses_percent; +$tojson{pre_meta_misses_per}=$prefetch_metadata_misses_percent; + +#process each pool and shove them into JSON +my $zpool_output=`/sbin/zpool list -pH`; +my @pools=split( /\n/, $zpool_output ); +my $pools_int=0; +my @toShoveIntoJSON; +while ( defined( $pools[$pools_int] ) ) { + my %newPool; + + my $pool=$pools[$pools_int]; + $pool =~ s/\t/,/g; + $pool =~ s/\,\-\,/\,0\,/g; + $pool =~ s/\%//g; + $pool =~ s/\,([0-1\.]*)x\,/,$1,/; + + ( $newPool{name}, $newPool{size}, $newPool{alloc}, $newPool{free}, $newPool{expandsz}, $newPool{frag}, $newPool{cap}, $newPool{dedup} )=split(/\,/, $pool); + + push(@toShoveIntoJSON, \%newPool); + + $pools_int++; +} +$tojson{pools}=\@toShoveIntoJSON; + +my $j=JSON->new; + +if ( $opts{p} ){ + $j->pretty(1); +} + +print $j->encode( \%tojson ); + +if (! $opts{p} ){ + print "\n"; +} + +exit 0;