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