#!/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.c"} ){ $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;