#! /bin/sh

#
#	zfs_stats
#
#	This shell script retrieves information critical to tuning ZFS, and
#	displays it to the user.  It also performs a few simple checks to
#	make some preliminary suggestions.
#

#
#	History:
#
#	2014-05-24 - Add reporting of L2ARC hit rate
#	2014-05-22 - Initial release


#	This flag tracks whether or not the data from the OS is good enough
#	to consider the results of the script to be valid.  Possible causes
#	for setting the flag are:
#
#	Not enough name lookups (should be 100 * NCSIZE)
#	No Ghost activity
#
NOT_VALID=0


SetNotValid () {
    echo
    echo WARNING:
    echo
    echo The system may not have been up long enough to have valid data to be
    echo checked.  The decisions made by this script might not be optimal.
    echo
    NOT_VALID=1
}


#	For some reason, Oracle chose to display some of the information as Hex.
#	This function converts Hex input values to decimal.
HexToDec () {
    TEMP=`echo ${1} | tr -s '[:lower:]' '[:upper:]'`
    echo "16 i ${TEMP} p" | dc
}


CheckNameCache () {
    if [ ${NOT_VALID} = 0 ]
    then
	LOOKUPS=`expr ${LOOKUPS} / 100`
	if [ ${LOOKUPS} -lt ${NCSIZE} ]
	then
	    SetNotValid
	fi
    fi
    grep "total name lookups" /tmp/vmstat.$$
    if [ ${DNLC} -lt 80 ]
    then
	echo "ncsize (${NCSIZE}) should be larger".
    elif [ ${DNLC} -lt 90 ]
    then
	echo "ncsize (${NCSIZE}) may need to be larger".
    else
	echo "ncsize (${NCSIZE}) is large enough".
    fi
}


CheckL2ARC () {
    if [ ${NOT_VALID} = 0 ]
    then
	if [ ${MFU_GHOST} = 0 -o ${MRU_GHOST} = 0 -o ${MISSES} = 0 ]
	then
	    SetNotValid
	fi
    fi
    MFU_GHOST=`expr ${MFU_GHOST} \* 100`
    MRU_GHOST=`expr ${MRU_GHOST} \* 100`
    GHOST=`expr ${MFU_GHOST} + ${MRU_GHOST} / ${MISSES}`
    echo Ghost hit rate is ${GHOST}%, MRU ghost hit rate is `expr ${MRU_GHOST} / ${MISSES}`%, MFU ghost hit rate is `expr ${MFU_GHOST} / ${MISSES}`%.
    if [ ${GHOST} -gt 20 ]
    then
	echo An L2ARC will help performance.
    elif [ ${GHOST} -gt 10 ]
    then
	echo An L2ARC may help performance.
    else
	echo An L2ARC will not help performance.
    fi
    echo L2ARC hit rate is `expr $L2_HITS \* 100 / \( $L2_HITS + $L2_MISSES \)`%.
}


CheckQueueLength () {
    if [ ${ZFS_QUEUE} -gt 10 ]
    then
	echo ZFS queue depth optimized for RAID/SAN storage.
    elif [ ${ZFS_QUEUE} -lt 10 ]
    then
	echo ZFS queue depth optimized for use with an internal ZIL.
    else
	echo ZFS queue depth optimized for use with an external ZIL.
    fi

    if [ ${ZFS_QUEUE} -gt ${SD_THROTTLE} ]
    then
	echo "sd:sd_max_throttle needs to be at least ${ZFS_QUEUE}".
    fi

    if [ ${ZFS_QUEUE} -gt ${SSD_THROTTLE} ]
    then
	echo "ssd:ssd_max_throttle needs to be at least ${ZFS_QUEUE}".
    fi
}


CheckPrefetch () {
    if [ ${ZFS_PREFETCH} = 0 ]
    then
	PREFETCH_DATA_RATIO=`expr ${PREFETCH_DATA_HITS} \* 100 / \( ${PREFETCH_DATA_HITS} + ${PREFETCH_DATA_MISSES} \)`
	PREFETCH_METADATA_RATIO=`expr ${PREFETCH_METADATA_HITS} \* 100 / \( ${PREFETCH_METADATA_HITS} + ${PREFETCH_METADATA_MISSES} \)`
	PREFETCH_RATIO=`expr \( ${PREFETCH_DATA_HITS} + ${PREFETCH_METADATA_HITS} \) \* 100 / \( ${PREFETCH_DATA_HITS} + ${PREFETCH_DATA_MISSES} + ${PREFETCH_METADATA_HITS} + ${PREFETCH_METADATA_MISSES} \)`
	echo Prefetch hit ratio = ${PREFETCH_RATIO}%.
	echo Prefetch data hit ratio = ${PREFETCH_DATA_RATIO}%, prefetch metadata hit ratio = ${PREFETCH_METADATA_RATIO}%.
	if [ ${PREFETCH_RATIO} -lt 8 ]
	then
	    echo Prefetch should be disabled.
	elif [ ${PREFETCH_RATIO} -lt 12 ]
	then
	    echo Preferch may need to be disabled.
	else
	    echo Prefetch should remain enabled.
	fi
    else
	echo Prefetch is currently disabled.
    fi
}


ShowVDEVCache () {
    if [ ${ZFS_VDEV_CACHE_SIZE} = 0 ]
    then
    	echo The ZFS device prefetch cache is disabled.
    else
	echo Each ZFS device has ${ZFS_VDEV_CACHE_SIZE} bytes of prefetch cache.
	echo Each I/O smaller than ${ZFS_VDEV_CACHE_MAX} bytes will be expanded to ${ZFS_VDEV_CACHE_BSHIFT} bytes.
    fi
}


ShowARCInfo () {
    echo Physical memory size = ${PHYSMEM} bytes.
    echo Maximum ARC size = ${ZFS_ARC_MAX} bytes.
    echo Minimum ARC size = ${ZFS_ARC_MIN} bytes.
    echo Target ARC size = ${ARC_TARGET} bytes.
    echo Current ARC size = ${ARC_SIZE} bytes.
    USE=`expr ${ARC_SIZE} \* 100 / ${ARC_TARGET}`
    echo ARC use = ${USE}%
    if [ ${ZFS_NOCACHEFLUSH} = 0 ]
    then
    	echo ZFS is configured to send cache flush commands to disks.
    else
    	echo ZFS is configured to not send cache flush commands to disks "(RAID/SAN)".
    fi
    echo Dirty blocks in the ARC will be flushed every ${ZFS_TXG_TIMEOUT} seconds,
    echo or when the estimated write duration hits ${ZFS_TXG_SYNCTIME_MS} milli-seconds.
    if [ ${ZFS_NO_WRITE_THROTTLE} = 1 ]
    then
    	echo ZFS per-process write throttle has been disabled.
    else
    	echo ZFS per-process write throttle will be between ${ZFS_WRITE_LIMIT_MIN}B/S.
	echo and ${ZFS_WRITE_LIMIT_MAX}B/S, depending on overall system write load.
    fi
}


ShowL2ARCInfo () {
    if [ ${L2ARC_NORW} = 0 ]
    then
    	echo The L2ARC is configured for simultaneous reads and writes "(SSD)".
    else
    	echo The L2ARC is configured to isolate reads and writes "(disk)".
    fi
    echo The L2ARC is configured for ${L2ARC_HEADROOM} write buffers.
    echo The L2ARC maximum normal write buffer size is ${L2ARC_WRITE_MAX}B.
    echo The L2ARC maximum initializing write buffer size is ${L2ARC_WRITE_BOOST}B.
}


# Gather all needed information
echo "Gathering data"
# echo '::zfs_params' | mdb -k > /tmp/zfs_params.$$
cat << EOF | mdb -k 2>/dev/null > /tmp/zfs_params.$$
::zfs_params
ncsize/D
physmem/D
maxusers/D
sd_max_throttle/D
ssd_max_throttle/D
l2arc_norw/D
l2arc_headroom/D
l2arc_write_max/D
l2arc_write_boost/D
EOF

ZFS_QUEUE=`grep zfs_vdev_max_pending /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
ZFS_QUEUE=`HexToDec ${ZFS_QUEUE}`
ZFS_PREFETCH=`grep zfs_prefetch_disable /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
ZFS_PREFETCH=`HexToDec ${ZFS_PREFETCH}`
ZFS_VDEV_CACHE_SIZE=`grep zfs_vdev_cache_size /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
ZFS_VDEV_CACHE_SIZE=`HexToDec ${ZFS_VDEV_CACHE_SIZE}`
if [ ${ZFS_VDEV_CACHE_SIZE} != 0 ]
then
    ZFS_VDEV_CACHE_MAX=`grep zfs_vdev_cache_max /tmp/zfs_params.$$ | \
	sed -e 's/  *$//' -e 's/.*[ =:]//'`
    ZFS_VDEV_CACHE_MAX=`HexToDec ${ZFS_VDEV_CACHE_MAX}`
    ZFS_VDEV_CACHE_BSHIFT=`grep zfs_vdev_cache_bshift /tmp/zfs_params.$$ | \
	sed -e 's/  *$//' -e 's/.*[ =:]//'`
    ZFS_VDEV_CACHE_BSHIFT=`HexToDec ${ZFS_VDEV_CACHE_BSHIFT}`
    ZFS_VDEV_CACHE_BSHIFT=`echo 2 \^ ${ZFS_VDEV_CACHE_BSHIFT} | bc`
fi
ZFS_ARC_MAX=`grep zfs_arc_max /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
ZFS_ARC_MAX=`HexToDec ${ZFS_ARC_MAX}`
ZFS_ARC_MIN=`grep zfs_arc_min /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
ZFS_ARC_MIN=`HexToDec ${ZFS_ARC_MIN}`
ZFS_NOCACHEFLUSH=`grep zfs_nocacheflush /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
ZFS_NOCACHEFLUSH=`HexToDec ${ZFS_NOCACHEFLUSH}`
ZFS_TXG_TIMEOUT=`grep zfs_txg_timeout /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
ZFS_TXG_TIMEOUT=`HexToDec ${ZFS_TXG_TIMEOUT}`
ZFS_TXG_SYNCTIME_MS=`grep zfs_txg_synctime_ms /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
ZFS_TXG_SYNCTIME_MS=`HexToDec ${ZFS_TXG_SYNCTIME_MS}`
ZFS_NO_WRITE_THROTTLE=`grep zfs_no_write_throttle /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
ZFS_NO_WRITE_THROTTLE=`HexToDec ${ZFS_NO_WRITE_THROTTLE}`
ZFS_WRITE_LIMIT_MIN=`grep zfs_write_limit_min /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
ZFS_WRITE_LIMIT_MIN=`HexToDec ${ZFS_WRITE_LIMIT_MIN}`
ZFS_WRITE_LIMIT_MAX=`grep zfs_write_limit_max /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
ZFS_WRITE_LIMIT_MAX=`HexToDec ${ZFS_WRITE_LIMIT_MAX}`
NCSIZE=`grep "^ncsize:" /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//' | \
    tail -1`
PHYSMEM=`grep "^physmem:" /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//' | \
    tail -1`
PHYSMEM=`expr ${PHYSMEM} \* 4096`
MAXUSERS=`grep "^maxusers:" /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//' | \
    tail -1`
SD_THROTTLE=`grep "^sd_max_throttle:" /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//' | \
    tail -1`
SSD_THROTTLE=`grep "^ssd_max_throttle:" /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//' | \
    tail -1`
if [ -z "$SSD_THROTTLE" ]
then
    SSD_THROTTLE=${ZFS_QUEUE}
fi
L2ARC_NORW=`grep "^l2arc_norw:" /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//' | \
    tail -1`
L2ARC_HEADROOM=`grep "^l2arc_headroom:" /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//' | \
    tail -1`
L2ARC_WRITE_MAX=`grep "^l2arc_write_boost:" /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//' | \
    tail -1`
L2ARC_WRITE_BOOST=`grep "^l2arc_write_boost:" /tmp/zfs_params.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//' | \
    tail -1`

kstat -n arcstats | tr -s '\t' ' ' > /tmp/arcstats.$$
MFU_GHOST=`grep ' mfu_ghost_hits ' /tmp/arcstats.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
MRU_GHOST=`grep ' mru_ghost_hits ' /tmp/arcstats.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
MISSES=`grep ' misses ' /tmp/arcstats.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
PREFETCH_DATA_HITS=`grep ' prefetch_data_hits ' /tmp/arcstats.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
PREFETCH_DATA_MISSES=`grep ' prefetch_data_misses ' /tmp/arcstats.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
PREFETCH_METADATA_HITS=`grep ' prefetch_metadata_hits ' /tmp/arcstats.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
PREFETCH_METADATA_MISSES=`grep ' prefetch_metadata_misses ' /tmp/arcstats.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
ARC_TARGET=`grep ' c ' /tmp/arcstats.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
ARC_SIZE=`grep ' size ' /tmp/arcstats.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
L2_HITS=`grep ' l2_hits ' /tmp/arcstats.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`
L2_MISSES=`grep ' l2_misses ' /tmp/arcstats.$$ | \
    sed -e 's/  *$//' -e 's/.*[ =:]//'`

vmstat -s > /tmp/vmstat.$$
LOOKUPS=`grep "total name lookups" /tmp/vmstat.$$ | \
    sed -e 's/^ *//' -e 's/ .*//'`
DNLC=`grep "total name lookups" /tmp/vmstat.$$ | \
    sed -e 's/%.*//' -e 's/.* //'`

echo "Done"

# Must be first.
CheckNameCache

CheckL2ARC

CheckQueueLength

CheckPrefetch

ShowVDEVCache

ShowARCInfo

ShowL2ARCInfo

rm -f /tmp/*.$$
