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