xref: /freebsd-src/sys/contrib/openzfs/tests/zfs-tests/include/kstat.shlib (revision c6767dc1f236f20eecd75790afd42829345153da)
1*c6767dc1SMartin Matuska#
2*c6767dc1SMartin Matuska# CDDL HEADER START
3*c6767dc1SMartin Matuska#
4*c6767dc1SMartin Matuska# The contents of this file are subject to the terms of the
5*c6767dc1SMartin Matuska# Common Development and Distribution License (the "License").
6*c6767dc1SMartin Matuska# You may not use this file except in compliance with the License.
7*c6767dc1SMartin Matuska#
8*c6767dc1SMartin Matuska# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9*c6767dc1SMartin Matuska# or https://opensource.org/licenses/CDDL-1.0.
10*c6767dc1SMartin Matuska# See the License for the specific language governing permissions
11*c6767dc1SMartin Matuska# and limitations under the License.
12*c6767dc1SMartin Matuska#
13*c6767dc1SMartin Matuska# When distributing Covered Code, include this CDDL HEADER in each
14*c6767dc1SMartin Matuska# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15*c6767dc1SMartin Matuska# If applicable, add the following below this CDDL HEADER, with the
16*c6767dc1SMartin Matuska# fields enclosed by brackets "[]" replaced with your own identifying
17*c6767dc1SMartin Matuska# information: Portions Copyright [yyyy] [name of copyright owner]
18*c6767dc1SMartin Matuska#
19*c6767dc1SMartin Matuska# CDDL HEADER END
20*c6767dc1SMartin Matuska#
21*c6767dc1SMartin Matuska
22*c6767dc1SMartin Matuska#
23*c6767dc1SMartin Matuska# Copyright (c) 2025, Klara, Inc.
24*c6767dc1SMartin Matuska#
25*c6767dc1SMartin Matuska
26*c6767dc1SMartin Matuska#
27*c6767dc1SMartin Matuska# This file provides the following helpers to read kstats from tests.
28*c6767dc1SMartin Matuska#
29*c6767dc1SMartin Matuska#   kstat [-g] <stat>
30*c6767dc1SMartin Matuska#   kstat_pool [-g] <pool> <stat>
31*c6767dc1SMartin Matuska#   kstat_dataset [-N] <dataset | pool/objsetid> <stat>
32*c6767dc1SMartin Matuska#
33*c6767dc1SMartin Matuska# `kstat` and `kstat_pool` return the value of of the given <stat>, either
34*c6767dc1SMartin Matuska# a global or pool-specific state.
35*c6767dc1SMartin Matuska#
36*c6767dc1SMartin Matuska#   $ kstat dbgmsg
37*c6767dc1SMartin Matuska#   timestamp    message
38*c6767dc1SMartin Matuska#   1736848201   spa_history.c:304:spa_history_log_sync(): txg 14734896 ...
39*c6767dc1SMartin Matuska#   1736848201   spa_history.c:330:spa_history_log_sync(): ioctl ...
40*c6767dc1SMartin Matuska#   ...
41*c6767dc1SMartin Matuska#
42*c6767dc1SMartin Matuska#   $ kstat_pool garden state
43*c6767dc1SMartin Matuska#   ONLINE
44*c6767dc1SMartin Matuska#
45*c6767dc1SMartin Matuska# To get a single stat within a group or collection, separate the name with
46*c6767dc1SMartin Matuska# '.' characters.
47*c6767dc1SMartin Matuska#
48*c6767dc1SMartin Matuska#   $ kstat dbufstats.cache_target_bytes
49*c6767dc1SMartin Matuska#   3215780693
50*c6767dc1SMartin Matuska#
51*c6767dc1SMartin Matuska#   $ kstat_pool crayon iostats.arc_read_bytes
52*c6767dc1SMartin Matuska#   253671670784
53*c6767dc1SMartin Matuska#
54*c6767dc1SMartin Matuska# -g is "group" mode. If the kstat is a group or collection, all stats in that
55*c6767dc1SMartin Matuska# group are returned, one stat per line, key and value separated by a space.
56*c6767dc1SMartin Matuska#
57*c6767dc1SMartin Matuska#   $ kstat -g dbufstats
58*c6767dc1SMartin Matuska#   cache_count 1792
59*c6767dc1SMartin Matuska#   cache_size_bytes 87720376
60*c6767dc1SMartin Matuska#   cache_size_bytes_max 305187768
61*c6767dc1SMartin Matuska#   cache_target_bytes 97668555
62*c6767dc1SMartin Matuska#   ...
63*c6767dc1SMartin Matuska#
64*c6767dc1SMartin Matuska#   $ kstat_pool -g crayon iostats
65*c6767dc1SMartin Matuska#   trim_extents_written 0
66*c6767dc1SMartin Matuska#   trim_bytes_written 0
67*c6767dc1SMartin Matuska#   trim_extents_skipped 0
68*c6767dc1SMartin Matuska#   trim_bytes_skipped 0
69*c6767dc1SMartin Matuska#   ...
70*c6767dc1SMartin Matuska#
71*c6767dc1SMartin Matuska# `kstat_dataset` accesses the per-dataset group kstat. The dataset can be
72*c6767dc1SMartin Matuska# specified by name:
73*c6767dc1SMartin Matuska#
74*c6767dc1SMartin Matuska#   $ kstat_dataset crayon/home/robn nunlinks
75*c6767dc1SMartin Matuska#   2628514
76*c6767dc1SMartin Matuska#
77*c6767dc1SMartin Matuska# or, with the -N switch, as <pool>/<objsetID>:
78*c6767dc1SMartin Matuska#
79*c6767dc1SMartin Matuska#   $ kstat_dataset -N crayon/7 writes
80*c6767dc1SMartin Matuska#   125135
81*c6767dc1SMartin Matuska#
82*c6767dc1SMartin Matuska
83*c6767dc1SMartin Matuska####################
84*c6767dc1SMartin Matuska# Public interface
85*c6767dc1SMartin Matuska
86*c6767dc1SMartin Matuska#
87*c6767dc1SMartin Matuska# kstat [-g] <stat>
88*c6767dc1SMartin Matuska#
89*c6767dc1SMartin Matuskafunction kstat
90*c6767dc1SMartin Matuska{
91*c6767dc1SMartin Matuska	typeset -i want_group=0
92*c6767dc1SMartin Matuska
93*c6767dc1SMartin Matuska	OPTIND=1
94*c6767dc1SMartin Matuska	while getopts "g" opt ; do
95*c6767dc1SMartin Matuska		case $opt in
96*c6767dc1SMartin Matuska			'g') want_group=1 ;;
97*c6767dc1SMartin Matuska			*) log_fail "kstat: invalid option '$opt'" ;;
98*c6767dc1SMartin Matuska		esac
99*c6767dc1SMartin Matuska	done
100*c6767dc1SMartin Matuska	shift $(expr $OPTIND - 1)
101*c6767dc1SMartin Matuska
102*c6767dc1SMartin Matuska	typeset stat=$1
103*c6767dc1SMartin Matuska
104*c6767dc1SMartin Matuska	$_kstat_os 'global' '' "$stat" $want_group
105*c6767dc1SMartin Matuska}
106*c6767dc1SMartin Matuska
107*c6767dc1SMartin Matuska#
108*c6767dc1SMartin Matuska# kstat_pool [-g] <pool> <stat>
109*c6767dc1SMartin Matuska#
110*c6767dc1SMartin Matuskafunction kstat_pool
111*c6767dc1SMartin Matuska{
112*c6767dc1SMartin Matuska	typeset -i want_group=0
113*c6767dc1SMartin Matuska
114*c6767dc1SMartin Matuska	OPTIND=1
115*c6767dc1SMartin Matuska	while getopts "g" opt ; do
116*c6767dc1SMartin Matuska		case $opt in
117*c6767dc1SMartin Matuska			'g') want_group=1 ;;
118*c6767dc1SMartin Matuska			*) log_fail "kstat_pool: invalid option '$opt'" ;;
119*c6767dc1SMartin Matuska		esac
120*c6767dc1SMartin Matuska	done
121*c6767dc1SMartin Matuska	shift $(expr $OPTIND - 1)
122*c6767dc1SMartin Matuska
123*c6767dc1SMartin Matuska	typeset pool=$1
124*c6767dc1SMartin Matuska	typeset stat=$2
125*c6767dc1SMartin Matuska
126*c6767dc1SMartin Matuska	$_kstat_os 'pool' "$pool" "$stat" $want_group
127*c6767dc1SMartin Matuska}
128*c6767dc1SMartin Matuska
129*c6767dc1SMartin Matuska#
130*c6767dc1SMartin Matuska# kstat_dataset [-N] <dataset | pool/objsetid> <stat>
131*c6767dc1SMartin Matuska#
132*c6767dc1SMartin Matuskafunction kstat_dataset
133*c6767dc1SMartin Matuska{
134*c6767dc1SMartin Matuska	typeset -i opt_objsetid=0
135*c6767dc1SMartin Matuska
136*c6767dc1SMartin Matuska	OPTIND=1
137*c6767dc1SMartin Matuska	while getopts "N" opt ; do
138*c6767dc1SMartin Matuska		case $opt in
139*c6767dc1SMartin Matuska			'N') opt_objsetid=1 ;;
140*c6767dc1SMartin Matuska			*) log_fail "kstat_dataset: invalid option '$opt'" ;;
141*c6767dc1SMartin Matuska		esac
142*c6767dc1SMartin Matuska	done
143*c6767dc1SMartin Matuska	shift $(expr $OPTIND - 1)
144*c6767dc1SMartin Matuska
145*c6767dc1SMartin Matuska	typeset dsarg=$1
146*c6767dc1SMartin Matuska	typeset stat=$2
147*c6767dc1SMartin Matuska
148*c6767dc1SMartin Matuska	if [[ $opt_objsetid == 0 ]] ; then
149*c6767dc1SMartin Matuska		typeset pool="${dsarg%%/*}"	# clear first / -> end
150*c6767dc1SMartin Matuska		typeset objsetid=$($_resolve_dsname_os "$pool" "$dsarg")
151*c6767dc1SMartin Matuska		if [[ -z "$objsetid" ]] ; then
152*c6767dc1SMartin Matuska			log_fail "kstat_dataset: dataset not found: $dsarg"
153*c6767dc1SMartin Matuska		fi
154*c6767dc1SMartin Matuska		dsarg="$pool/$objsetid"
155*c6767dc1SMartin Matuska	fi
156*c6767dc1SMartin Matuska
157*c6767dc1SMartin Matuska	$_kstat_os 'dataset' "$dsarg" "$stat" 0
158*c6767dc1SMartin Matuska}
159*c6767dc1SMartin Matuska
160*c6767dc1SMartin Matuska####################
161*c6767dc1SMartin Matuska# Platform-specific interface
162*c6767dc1SMartin Matuska
163*c6767dc1SMartin Matuska#
164*c6767dc1SMartin Matuska# Implementation notes
165*c6767dc1SMartin Matuska#
166*c6767dc1SMartin Matuska# There's not a lot of uniformity between platforms, so I've written to a rough
167*c6767dc1SMartin Matuska# imagined model that seems to fit the majority of OpenZFS kstats.
168*c6767dc1SMartin Matuska#
169*c6767dc1SMartin Matuska# The main platform entry points look like this:
170*c6767dc1SMartin Matuska#
171*c6767dc1SMartin Matuska#    _kstat_freebsd <scope> <object> <stat> <want_group>
172*c6767dc1SMartin Matuska#    _kstat_linux <scope> <object> <stat> <want_group>
173*c6767dc1SMartin Matuska#
174*c6767dc1SMartin Matuska# - scope: one of 'global', 'pool', 'dataset'. The "kind" of object the kstat
175*c6767dc1SMartin Matuska#          is attached to.
176*c6767dc1SMartin Matuska# - object: name of the scoped object
177*c6767dc1SMartin Matuska#           global:  empty string
178*c6767dc1SMartin Matuska#           pool:    pool name
179*c6767dc1SMartin Matuska#           dataset: <pool>/<objsetId> pair
180*c6767dc1SMartin Matuska# - stat: kstat name to get
181*c6767dc1SMartin Matuska# - want_group: 0 to get the single value for the kstat, 1 to treat the kstat
182*c6767dc1SMartin Matuska#               as a group and get all the stat names+values under it. group
183*c6767dc1SMartin Matuska#               kstats cannot have values, and stat kstats cannot have
184*c6767dc1SMartin Matuska#               children (by definition)
185*c6767dc1SMartin Matuska#
186*c6767dc1SMartin Matuska# Stat values can have multiple lines, so be prepared for those.
187*c6767dc1SMartin Matuska#
188*c6767dc1SMartin Matuska# These functions either succeed and produce the requested output, or call
189*c6767dc1SMartin Matuska# log_fail. They should never output empty, or 0, or anything else.
190*c6767dc1SMartin Matuska#
191*c6767dc1SMartin Matuska# Output:
192*c6767dc1SMartin Matuska#
193*c6767dc1SMartin Matuska# - want_group=0: the single stat value, followed by newline
194*c6767dc1SMartin Matuska# - want_group=1: One stat per line, <name><SP><value><newline>
195*c6767dc1SMartin Matuska#
196*c6767dc1SMartin Matuska
197*c6767dc1SMartin Matuska#
198*c6767dc1SMartin Matuska# To support kstat_dataset(), platforms also need to provide a dataset
199*c6767dc1SMartin Matuska# name->object id resolver function.
200*c6767dc1SMartin Matuska#
201*c6767dc1SMartin Matuska#   _resolve_dsname_freebsd <pool> <dsname>
202*c6767dc1SMartin Matuska#   _resolve_dsname_linux <pool> <dsname>
203*c6767dc1SMartin Matuska#
204*c6767dc1SMartin Matuska# - pool: pool name. always the first part of the dataset name
205*c6767dc1SMartin Matuska# - dsname: dataset name, in the standard <pool>/<some>/<dataset> format.
206*c6767dc1SMartin Matuska#
207*c6767dc1SMartin Matuska# Output is <objsetID>. objsetID is a decimal integer, > 0
208*c6767dc1SMartin Matuska#
209*c6767dc1SMartin Matuska
210*c6767dc1SMartin Matuska####################
211*c6767dc1SMartin Matuska# FreeBSD
212*c6767dc1SMartin Matuska
213*c6767dc1SMartin Matuska#
214*c6767dc1SMartin Matuska# All kstats are accessed through sysctl. We model "groups" as interior nodes
215*c6767dc1SMartin Matuska# in the stat tree, which are normally opaque. Because sysctl has no filtering
216*c6767dc1SMartin Matuska# options, and requesting any node produces all nodes below it, we have to
217*c6767dc1SMartin Matuska# always get the name and value, and then consider the output to understand
218*c6767dc1SMartin Matuska# if we got a group or a single stat, and post-process accordingly.
219*c6767dc1SMartin Matuska#
220*c6767dc1SMartin Matuska# Scopes are mostly mapped directly to known locations in the tree, but there
221*c6767dc1SMartin Matuska# are a handful of stats that are out of position, so we need to adjust.
222*c6767dc1SMartin Matuska#
223*c6767dc1SMartin Matuska
224*c6767dc1SMartin Matuska#
225*c6767dc1SMartin Matuska# _kstat_freebsd <scope> <object> <stat> <want_group>
226*c6767dc1SMartin Matuska#
227*c6767dc1SMartin Matuskafunction _kstat_freebsd
228*c6767dc1SMartin Matuska{
229*c6767dc1SMartin Matuska	typeset scope=$1
230*c6767dc1SMartin Matuska	typeset obj=$2
231*c6767dc1SMartin Matuska	typeset stat=$3
232*c6767dc1SMartin Matuska	typeset -i want_group=$4
233*c6767dc1SMartin Matuska
234*c6767dc1SMartin Matuska	typeset oid=""
235*c6767dc1SMartin Matuska	case "$scope" in
236*c6767dc1SMartin Matuska	global)
237*c6767dc1SMartin Matuska		oid="kstat.zfs.misc.$stat"
238*c6767dc1SMartin Matuska		;;
239*c6767dc1SMartin Matuska	pool)
240*c6767dc1SMartin Matuska		# For reasons unknown, the "multihost", "txgs" and "reads"
241*c6767dc1SMartin Matuska		# pool-specific kstats are directly under kstat.zfs.<pool>,
242*c6767dc1SMartin Matuska		# rather than kstat.zfs.<pool>.misc like the other pool kstats.
243*c6767dc1SMartin Matuska		# Adjust for that here.
244*c6767dc1SMartin Matuska		case "$stat" in
245*c6767dc1SMartin Matuska		multihost|txgs|reads)
246*c6767dc1SMartin Matuska		    oid="kstat.zfs.$obj.$stat"
247*c6767dc1SMartin Matuska		    ;;
248*c6767dc1SMartin Matuska		*)
249*c6767dc1SMartin Matuska		    oid="kstat.zfs.$obj.misc.$stat"
250*c6767dc1SMartin Matuska		    ;;
251*c6767dc1SMartin Matuska		esac
252*c6767dc1SMartin Matuska		;;
253*c6767dc1SMartin Matuska	dataset)
254*c6767dc1SMartin Matuska		typeset pool=""
255*c6767dc1SMartin Matuska		typeset -i objsetid=0
256*c6767dc1SMartin Matuska		_split_pool_objsetid $obj pool objsetid
257*c6767dc1SMartin Matuska		oid=$(printf 'kstat.zfs.%s.dataset.objset-0x%x.%s' \
258*c6767dc1SMartin Matuska		    $pool $objsetid $stat)
259*c6767dc1SMartin Matuska		;;
260*c6767dc1SMartin Matuska	esac
261*c6767dc1SMartin Matuska
262*c6767dc1SMartin Matuska	# Calling sysctl on a "group" node will return everything under that
263*c6767dc1SMartin Matuska	# node, so we have to inspect the first line to make sure we are
264*c6767dc1SMartin Matuska	# getting back what we expect. For a single value, the key will have
265*c6767dc1SMartin Matuska	# the name we requested, while for a group, the key will not have the
266*c6767dc1SMartin Matuska	# name (group nodes are "opaque", not returned by sysctl by default.
267*c6767dc1SMartin Matuska
268*c6767dc1SMartin Matuska	if [[ $want_group == 0 ]] ; then
269*c6767dc1SMartin Matuska		sysctl -e "$oid" | awk -v oid="$oid" -v oidre="^$oid=" '
270*c6767dc1SMartin Matuska			NR == 1 && $0 !~ oidre { exit 1 }
271*c6767dc1SMartin Matuska			NR == 1 { print substr($0, length(oid)+2) ; next }
272*c6767dc1SMartin Matuska			{ print }
273*c6767dc1SMartin Matuska		'
274*c6767dc1SMartin Matuska	else
275*c6767dc1SMartin Matuska		sysctl -e "$oid" | awk -v oid="$oid" -v oidre="^$oid=" '
276*c6767dc1SMartin Matuska			NR == 1 && $0 ~ oidre { exit 2 }
277*c6767dc1SMartin Matuska			{
278*c6767dc1SMartin Matuska			    sub("^" oid "\.", "")
279*c6767dc1SMartin Matuska			    sub("=", " ")
280*c6767dc1SMartin Matuska			    print
281*c6767dc1SMartin Matuska			}
282*c6767dc1SMartin Matuska		'
283*c6767dc1SMartin Matuska	fi
284*c6767dc1SMartin Matuska
285*c6767dc1SMartin Matuska	typeset -i err=$?
286*c6767dc1SMartin Matuska	case $err in
287*c6767dc1SMartin Matuska		0) return ;;
288*c6767dc1SMartin Matuska		1) log_fail "kstat: can't get value for group kstat: $oid" ;;
289*c6767dc1SMartin Matuska		2) log_fail "kstat: not a group kstat: $oid" ;;
290*c6767dc1SMartin Matuska	esac
291*c6767dc1SMartin Matuska
292*c6767dc1SMartin Matuska	log_fail "kstat: unknown error: $oid"
293*c6767dc1SMartin Matuska}
294*c6767dc1SMartin Matuska
295*c6767dc1SMartin Matuska#
296*c6767dc1SMartin Matuska#   _resolve_dsname_freebsd <pool> <dsname>
297*c6767dc1SMartin Matuska#
298*c6767dc1SMartin Matuskafunction _resolve_dsname_freebsd
299*c6767dc1SMartin Matuska{
300*c6767dc1SMartin Matuska	# we're searching for:
301*c6767dc1SMartin Matuska	#
302*c6767dc1SMartin Matuska	# kstat.zfs.shed.dataset.objset-0x8087.dataset_name: shed/poudriere
303*c6767dc1SMartin Matuska	#
304*c6767dc1SMartin Matuska	# We split on '.', then get the hex objsetid from field 5.
305*c6767dc1SMartin Matuska	#
306*c6767dc1SMartin Matuska	# We convert hex to decimal in the shell because there isn't a _simple_
307*c6767dc1SMartin Matuska	# portable way to do it in awk and this code is already too intense to
308*c6767dc1SMartin Matuska	# do it a complicated way.
309*c6767dc1SMartin Matuska	typeset pool=$1
310*c6767dc1SMartin Matuska	typeset dsname=$2
311*c6767dc1SMartin Matuska	sysctl -e kstat.zfs.$pool | \
312*c6767dc1SMartin Matuska	    awk -F '.' -v dsnamere="=$dsname$" '
313*c6767dc1SMartin Matuska		/\.objset-0x[0-9a-f]+\.dataset_name=/ && $6 ~ dsnamere {
314*c6767dc1SMartin Matuska		    print substr($5, 8)
315*c6767dc1SMartin Matuska		    exit
316*c6767dc1SMartin Matuska		}
317*c6767dc1SMartin Matuska	    ' | xargs printf %d
318*c6767dc1SMartin Matuska}
319*c6767dc1SMartin Matuska
320*c6767dc1SMartin Matuska####################
321*c6767dc1SMartin Matuska# Linux
322*c6767dc1SMartin Matuska
323*c6767dc1SMartin Matuska#
324*c6767dc1SMartin Matuska# kstats all live under /proc/spl/kstat/zfs. They have a flat structure: global
325*c6767dc1SMartin Matuska# at top-level, pool in a directory, and dataset in a objset- file inside the
326*c6767dc1SMartin Matuska# pool dir.
327*c6767dc1SMartin Matuska#
328*c6767dc1SMartin Matuska# Groups are challenge. A single stat can be the entire text of a file, or
329*c6767dc1SMartin Matuska# a single line that must be extracted from a "group" file. The only way to
330*c6767dc1SMartin Matuska# recognise a group from the outside is to look for its header. This naturally
331*c6767dc1SMartin Matuska# breaks if a raw file had a matching header, or if a group file chooses to
332*c6767dc1SMartin Matuska# hid its header. Fortunately OpenZFS does none of these things at the moment.
333*c6767dc1SMartin Matuska#
334*c6767dc1SMartin Matuska
335*c6767dc1SMartin Matuska#
336*c6767dc1SMartin Matuska# _kstat_linux <scope> <object> <stat> <want_group>
337*c6767dc1SMartin Matuska#
338*c6767dc1SMartin Matuskafunction _kstat_linux
339*c6767dc1SMartin Matuska{
340*c6767dc1SMartin Matuska	typeset scope=$1
341*c6767dc1SMartin Matuska	typeset obj=$2
342*c6767dc1SMartin Matuska	typeset stat=$3
343*c6767dc1SMartin Matuska	typeset -i want_group=$4
344*c6767dc1SMartin Matuska
345*c6767dc1SMartin Matuska	typeset singlestat=""
346*c6767dc1SMartin Matuska
347*c6767dc1SMartin Matuska	if [[ $scope == 'dataset' ]] ; then
348*c6767dc1SMartin Matuska		typeset pool=""
349*c6767dc1SMartin Matuska		typeset -i objsetid=0
350*c6767dc1SMartin Matuska		_split_pool_objsetid $obj pool objsetid
351*c6767dc1SMartin Matuska		stat=$(printf 'objset-0x%x.%s' $objsetid $stat)
352*c6767dc1SMartin Matuska		obj=$pool
353*c6767dc1SMartin Matuska		scope='pool'
354*c6767dc1SMartin Matuska	fi
355*c6767dc1SMartin Matuska
356*c6767dc1SMartin Matuska	typeset path=""
357*c6767dc1SMartin Matuska	if [[ $scope == 'global' ]] ; then
358*c6767dc1SMartin Matuska		path="/proc/spl/kstat/zfs/$stat"
359*c6767dc1SMartin Matuska	else
360*c6767dc1SMartin Matuska		path="/proc/spl/kstat/zfs/$obj/$stat"
361*c6767dc1SMartin Matuska	fi
362*c6767dc1SMartin Matuska
363*c6767dc1SMartin Matuska	if [[ ! -e "$path" && $want_group -eq 0 ]] ; then
364*c6767dc1SMartin Matuska		# This single stat doesn't have its own file, but the wanted
365*c6767dc1SMartin Matuska		# stat could be in a group kstat file, which we now need to
366*c6767dc1SMartin Matuska		# find. To do this, we split a single stat name into two parts:
367*c6767dc1SMartin Matuska		# the file that would contain the stat, and the key within that
368*c6767dc1SMartin Matuska		# file to match on. This works by converting all bar the last
369*c6767dc1SMartin Matuska		# '.' separator to '/', then splitting on the remaining '.'
370*c6767dc1SMartin Matuska		# separator. If there are no '.' separators, the second arg
371*c6767dc1SMartin Matuska		# returned will be empty.
372*c6767dc1SMartin Matuska		#
373*c6767dc1SMartin Matuska		#   foo              -> (foo)
374*c6767dc1SMartin Matuska		#   foo.bar          -> (foo, bar)
375*c6767dc1SMartin Matuska		#   foo.bar.baz      -> (foo/bar, baz)
376*c6767dc1SMartin Matuska		#   foo.bar.baz.quux -> (foo/bar/baz, quux)
377*c6767dc1SMartin Matuska		#
378*c6767dc1SMartin Matuska		# This is how we will target single stats within a larger NAMED
379*c6767dc1SMartin Matuska		# kstat file, eg dbufstats.cache_target_bytes.
380*c6767dc1SMartin Matuska		typeset -a split=($(echo "$stat" | \
381*c6767dc1SMartin Matuska		    sed -E 's/^(.+)\.([^\.]+)$/\1 \2/ ; s/\./\//g'))
382*c6767dc1SMartin Matuska		typeset statfile=${split[0]}
383*c6767dc1SMartin Matuska		singlestat=${split[1]:-""}
384*c6767dc1SMartin Matuska
385*c6767dc1SMartin Matuska		if [[ $scope == 'global' ]] ; then
386*c6767dc1SMartin Matuska			path="/proc/spl/kstat/zfs/$statfile"
387*c6767dc1SMartin Matuska		else
388*c6767dc1SMartin Matuska			path="/proc/spl/kstat/zfs/$obj/$statfile"
389*c6767dc1SMartin Matuska		fi
390*c6767dc1SMartin Matuska	fi
391*c6767dc1SMartin Matuska	if [[ ! -r "$path" ]] ; then
392*c6767dc1SMartin Matuska		log_fail "kstat: can't read $path"
393*c6767dc1SMartin Matuska	fi
394*c6767dc1SMartin Matuska
395*c6767dc1SMartin Matuska	if [[ $want_group == 1 ]] ; then
396*c6767dc1SMartin Matuska		# "group" (NAMED) kstats on Linux start:
397*c6767dc1SMartin Matuska		#
398*c6767dc1SMartin Matuska		#   $ cat /proc/spl/kstat/zfs/crayon/iostats
399*c6767dc1SMartin Matuska		#   70 1 0x01 26 7072 8577844978 661416318663496
400*c6767dc1SMartin Matuska		#   name                            type data
401*c6767dc1SMartin Matuska		#   trim_extents_written            4    0
402*c6767dc1SMartin Matuska		#   trim_bytes_written              4    0
403*c6767dc1SMartin Matuska		#
404*c6767dc1SMartin Matuska		# The second value on the first row is the ks_type. Group
405*c6767dc1SMartin Matuska		# mode only works for type 1, KSTAT_TYPE_NAMED. So we check
406*c6767dc1SMartin Matuska		# for that, and eject if it's the wrong type. Otherwise, we
407*c6767dc1SMartin Matuska		# skip the header row and process the values.
408*c6767dc1SMartin Matuska		awk '
409*c6767dc1SMartin Matuska			NR == 1 && ! /^[0-9]+ 1 / { exit 2 }
410*c6767dc1SMartin Matuska			NR < 3 { next }
411*c6767dc1SMartin Matuska			{ print $1 " " $NF }
412*c6767dc1SMartin Matuska		' "$path"
413*c6767dc1SMartin Matuska	elif [[ -n $singlestat ]] ; then
414*c6767dc1SMartin Matuska		# single stat. must be a single line within a group stat, so
415*c6767dc1SMartin Matuska		# we look for the header again as above.
416*c6767dc1SMartin Matuska		awk -v singlestat="$singlestat" \
417*c6767dc1SMartin Matuska		    -v singlestatre="^$singlestat " '
418*c6767dc1SMartin Matuska			NR == 1 && /^[0-9]+ [^1] / { exit 2 }
419*c6767dc1SMartin Matuska			NR < 3 { next }
420*c6767dc1SMartin Matuska			$0 ~ singlestatre { print $NF ; exit 0 }
421*c6767dc1SMartin Matuska			ENDFILE { exit 3 }
422*c6767dc1SMartin Matuska		' "$path"
423*c6767dc1SMartin Matuska	else
424*c6767dc1SMartin Matuska		# raw stat. dump contents, exclude group stats
425*c6767dc1SMartin Matuska		awk '
426*c6767dc1SMartin Matuska			NR == 1 && /^[0-9]+ 1 / { exit 1 }
427*c6767dc1SMartin Matuska			{ print }
428*c6767dc1SMartin Matuska		' "$path"
429*c6767dc1SMartin Matuska	fi
430*c6767dc1SMartin Matuska
431*c6767dc1SMartin Matuska	typeset -i err=$?
432*c6767dc1SMartin Matuska	case $err in
433*c6767dc1SMartin Matuska		0) return ;;
434*c6767dc1SMartin Matuska		1) log_fail "kstat: can't get value for group kstat: $path" ;;
435*c6767dc1SMartin Matuska		2) log_fail "kstat: not a group kstat: $path" ;;
436*c6767dc1SMartin Matuska		3) log_fail "kstat: stat not found in group: $path $singlestat" ;;
437*c6767dc1SMartin Matuska	esac
438*c6767dc1SMartin Matuska
439*c6767dc1SMartin Matuska	log_fail "kstat: unknown error: $path"
440*c6767dc1SMartin Matuska}
441*c6767dc1SMartin Matuska
442*c6767dc1SMartin Matuska#
443*c6767dc1SMartin Matuska#   _resolve_dsname_linux <pool> <dsname>
444*c6767dc1SMartin Matuska#
445*c6767dc1SMartin Matuskafunction _resolve_dsname_linux
446*c6767dc1SMartin Matuska{
447*c6767dc1SMartin Matuska	# We look inside all:
448*c6767dc1SMartin Matuska	#
449*c6767dc1SMartin Matuska	#   /proc/spl/kstat/zfs/crayon/objset-0x113
450*c6767dc1SMartin Matuska	#
451*c6767dc1SMartin Matuska	# and check the dataset_name field inside. If we get a match, we split
452*c6767dc1SMartin Matuska	# the filename on /, then extract the hex objsetid.
453*c6767dc1SMartin Matuska	#
454*c6767dc1SMartin Matuska	# We convert hex to decimal in the shell because there isn't a _simple_
455*c6767dc1SMartin Matuska	# portable way to do it in awk and this code is already too intense to
456*c6767dc1SMartin Matuska	# do it a complicated way.
457*c6767dc1SMartin Matuska	typeset pool=$1
458*c6767dc1SMartin Matuska	typeset dsname=$2
459*c6767dc1SMartin Matuska	awk -v dsname="$dsname" '
460*c6767dc1SMartin Matuska	    $1 == "dataset_name" && $3 == dsname {
461*c6767dc1SMartin Matuska		split(FILENAME, a, "/")
462*c6767dc1SMartin Matuska		print substr(a[7], 8)
463*c6767dc1SMartin Matuska		exit
464*c6767dc1SMartin Matuska	    }
465*c6767dc1SMartin Matuska	    ' /proc/spl/kstat/zfs/$pool/objset-0x* | xargs printf %d
466*c6767dc1SMartin Matuska}
467*c6767dc1SMartin Matuska
468*c6767dc1SMartin Matuska####################
469*c6767dc1SMartin Matuska
470*c6767dc1SMartin Matuska#
471*c6767dc1SMartin Matuska# _split_pool_objsetid <obj> <*pool> <*objsetid>
472*c6767dc1SMartin Matuska#
473*c6767dc1SMartin Matuska# Splits pool/objsetId string in <obj> and fills <pool> and <objsetid>.
474*c6767dc1SMartin Matuska#
475*c6767dc1SMartin Matuskafunction _split_pool_objsetid
476*c6767dc1SMartin Matuska{
477*c6767dc1SMartin Matuska	typeset obj=$1
478*c6767dc1SMartin Matuska	typeset -n pool=$2
479*c6767dc1SMartin Matuska	typeset -n objsetid=$3
480*c6767dc1SMartin Matuska
481*c6767dc1SMartin Matuska	pool="${obj%%/*}"		# clear first / -> end
482*c6767dc1SMartin Matuska	typeset osidarg="${obj#*/}"	# clear start -> first /
483*c6767dc1SMartin Matuska
484*c6767dc1SMartin Matuska	# ensure objsetid arg does not contain a /. we're about to convert it,
485*c6767dc1SMartin Matuska	# but ksh will treat it as an expression, and a / will give a
486*c6767dc1SMartin Matuska	# divide-by-zero
487*c6767dc1SMartin Matuska	if [[ "${osidarg%%/*}" != "$osidarg" ]] ; then
488*c6767dc1SMartin Matuska		log_fail "kstat: invalid objsetid: $osidarg"
489*c6767dc1SMartin Matuska	fi
490*c6767dc1SMartin Matuska
491*c6767dc1SMartin Matuska	typeset -i id=$osidarg
492*c6767dc1SMartin Matuska	if [[ $id -le 0 ]] ; then
493*c6767dc1SMartin Matuska		log_fail "kstat: invalid objsetid: $osidarg"
494*c6767dc1SMartin Matuska	fi
495*c6767dc1SMartin Matuska	objsetid=$id
496*c6767dc1SMartin Matuska}
497*c6767dc1SMartin Matuska
498*c6767dc1SMartin Matuska####################
499*c6767dc1SMartin Matuska
500*c6767dc1SMartin Matuska#
501*c6767dc1SMartin Matuska# Per-platform function selection.
502*c6767dc1SMartin Matuska#
503*c6767dc1SMartin Matuska# To avoid needing platform check throughout, we store the names of the
504*c6767dc1SMartin Matuska# platform functions and call through them.
505*c6767dc1SMartin Matuska#
506*c6767dc1SMartin Matuskaif is_freebsd ; then
507*c6767dc1SMartin Matuska	_kstat_os='_kstat_freebsd'
508*c6767dc1SMartin Matuska	_resolve_dsname_os='_resolve_dsname_freebsd'
509*c6767dc1SMartin Matuskaelif is_linux ; then
510*c6767dc1SMartin Matuska	_kstat_os='_kstat_linux'
511*c6767dc1SMartin Matuska	_resolve_dsname_os='_resolve_dsname_linux'
512*c6767dc1SMartin Matuskaelse
513*c6767dc1SMartin Matuska	_kstat_os='_kstat_unknown_platform_implement_me'
514*c6767dc1SMartin Matuska	_resolve_dsname_os='_resolve_dsname_unknown_platform_implement_me'
515*c6767dc1SMartin Matuskafi
516*c6767dc1SMartin Matuska
517