Skip to content

Commit

Permalink
ZTS: reimplement kstat helper function
Browse files Browse the repository at this point in the history
The old kstat helper function was barely used, I suspect in part because
it was very limited in the kinds of kstats it could gather. This
rewrites it to handle most of the ways that tests want to read stats:

 - single value
 - all values within a "group"
 - global or per-pool

Most importantly, the interface is the same for both platforms.

Sponsored-by: Klara, Inc.
Sponsored-by: Wasabi Technology, Inc.
Signed-off-by: Rob Norris <[email protected]>
  • Loading branch information
robn committed Jan 15, 2025
1 parent fe44c5a commit 165d69b
Showing 1 changed file with 191 additions and 12 deletions.
203 changes: 191 additions & 12 deletions tests/zfs-tests/include/libtest.shlib
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
# Copyright (c) 2017, Datto Inc. All rights reserved.
# Copyright (c) 2017, Open-E Inc. All rights reserved.
# Copyright (c) 2021, The FreeBSD Foundation.
# Copyright (c) 2025, Klara, Inc.
# Use is subject to license terms.
#

Expand Down Expand Up @@ -3662,22 +3663,200 @@ function ls_xattr # path
esac
}

function kstat # stat flags?
#
# Gather data from ZFS kstats.
#
# kstat [-g] <stat> [<pool>]
#
# Returns the value of the <stat> kstat. If <pool> is provided, returns the
# value of the pool-specific kstat.
#
# $ kstat dbgmsg
# timestamp message
# ...
#
# $ kstat state garden
# ONLINE
#
# To get a single stat within a group or collection, separate the name with
# '.' characters.
#
# $ kstat dbufstats.cache_target_bytes
# 131162693
#
# $ kstat iostats.arc_read_bytes crayon
# 184371908096
#
# -g is "group" mode. If the kstat is a group or collection, all stats in that
# group are returned, one stat per line, key and value separated by a space.
#
# $ kstat -g dbufstats
# metadata_cache_overflow 839250601
# metadata_cache_size_bytes_max 1617221632
# metadata_cache_size_bytes 1617074176
# ...
#
function _kstat_freebsd
{
typeset stat=$1
typeset flags=${2-"-n"}
typeset pool=$2
typeset -i group_mode=$3

case "$UNAME" in
FreeBSD)
sysctl $flags kstat.zfs.misc.$stat
;;
Linux)
cat "/proc/spl/kstat/zfs/$stat" 2>/dev/null
;;
*)
false
;;
typeset oid=""
if [[ -z "$pool" ]] ; then
oid="kstat.zfs.misc.$stat"
else
oid="kstat.zfs.$pool.misc.$stat"
fi

# Calling sysctl on a "group" node will return everything under that
# node, so we have to inspect the first line to make sure we are
# getting back what we expect. For a single value, the key will have
# the name we requested, while for a group, the key will not have the
# name (group nodes are "opaque", not returned by sysctl by default.

if [[ $group_mode == 0 ]] ; then
sysctl -e "$oid" | awk -v oid="$oid" -v oidre="^$oid=" '
NR == 1 && $0 !~ oidre { exit 1 }
NR == 1 { print substr($0, length(oid)+2) ; next }
{ print }
'
else
sysctl -e "$oid" | awk -v oid="$oid" -v oidre="^$oid=" '
NR == 1 && $0 ~ oidre { exit 2 }
{
sub("^" oid "\.", "")
sub("=", " ")
print
}
'
fi

typeset -i err=$?
case $err in
0) return ;;
1) log_fail "kstat: can't get value for group kstat: $oid" ;;
2) log_fail "kstat: not a group kstat: $oid" ;;
esac

log_fail "kstat: unknown error: $oid"
}

function _kstat_linux
{
typeset stat=$1
typeset pool=$2
typeset -i group_mode=$3

typeset singlestat=""

typeset path=""
if [[ -z "$pool" ]] ; then
path="/proc/spl/kstat/zfs/$stat"
else
path="/proc/spl/kstat/zfs/$pool/$stat"
fi
if [[ ! -e "$path" && $group_mode -eq 0 ]] ; then
# If this is not group mode, then the stat might be a group
# kstat with a single stat within it. So, we split a single
# stat name into two parts: the file that would contain the
# stat, and the key within that file to match on. works by
# converting all bar the last '.' separator to '/', then
# splitting on the remaining '.' separator. If there are no '.'
# separators, the second arg returned will be empty.
#
# foo -> (foo)
# foo.bar -> (foo, bar)
# foo.bar.baz -> (foo/bar, baz)
# foo.bar.baz.quux -> (foo/bar/baz, quux)
#
# This is how we will target single stats within a larger NAMED
# kstat file, eg dbufstats.cache_target_bytes.
typeset -a split=($(echo "$stat" | \
sed -E 's/^(.+)\.([^\.]+)$/\1 \2/ ; s/\./\//g'))
typeset statfile=${split[0]}
singlestat=${split[1]:-""}

if [[ -z "$pool" ]] ; then
path="/proc/spl/kstat/zfs/$statfile"
else
path="/proc/spl/kstat/zfs/$pool/$statfile"
fi
fi
if [[ ! -r "$path" ]] ; then
log_fail "kstat: can't read $path"
fi

if [[ $group_mode == 1 ]] ; then
# "group" (NAMED) kstats on Linux start:
#
# $ cat /proc/spl/kstat/zfs/crayon/iostats
# 70 1 0x01 26 7072 8577844978 661416318663496
# name type data
# trim_extents_written 4 0
# trim_bytes_written 4 0
#
# The second value on the first row is the ks_type. Group
# mode only works for type 1, KSTAT_TYPE_NAMED. So we check
# for that, and eject if it's the wrong type. Otherwise, we
# skip the header row and process the values.
awk '
NR == 1 && ! /^[0-9]+ 1 / { exit 2 }
NR < 3 { next }
{ print $1 " " $3 }
' "$path"
elif [[ -n $singlestat ]] ; then
# single stat. must be a single line within a group stat, so
# we look for the header again as above.
awk -v singlestat="$singlestat" \
-v singlestatre="^$singlestat " '
NR == 1 && /^[0-9]+ [^1] / { exit 2 }
NR < 3 { next }
$0 ~ singlestatre { print $3 ; exit 0 }
ENDFILE { exit 3 }
' "$path"
else
# raw stat. dump contents, exclude group stats
awk '
NR == 1 && /^[0-9]+ 1 / { exit 1 }
{ print }
' "$path"
fi

typeset -i err=$?
case $err in
0) return ;;
1) log_fail "kstat: can't get value for group kstat: $path" ;;
2) log_fail "kstat: not a group kstat: $path" ;;
3) log_fail "kstat: stat not found in group: $path $singlestat" ;;
esac

log_fail "kstat: unknown error: $path"
}

function kstat
{
typeset -i group_mode=0

typeset OPTIND=1
while getopts "g" opt ; do
case $opt in
'g') group_mode=1 ;;
*) log_fail "kstat: invalid option '$opt'" ;;
esac
done
shift $(expr $OPTIND - 1)

typeset stat=$1
typeset pool=${2:-""}

if is_freebsd ; then
_kstat_freebsd "$stat" "$pool" $group_mode
elif is_linux ; then
_kstat_linux "$stat" "$pool" $group_mode
else
log_fail "kstat: no support for this platform: $UNAME"
fi
}

function get_arcstat # stat
Expand Down

0 comments on commit 165d69b

Please sign in to comment.