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