mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2026-05-22 02:27:36 +03:00
zpool iostat: refresh pool list every interval
When running zpool iostat in interval mode, it would not notice any new
pools created or imported, and would forget any destroyed or exported,
so would not notice if they came back. This leads to outputting "no
pools available" every interval until killed.
It looks like this was at least intended to work; the comment above
zpool_do_iostat() indicates that it is expected to "deal with pool
creation/destruction" and that pool_list_update() would detect new
pools. That call however was removed in 3e43edd2c5, though its unclear
if that broke this behaviour and it wasn't noticed, or if it never
worked, or if something later broke it. That said, the lack of
pool_list_update() is only part of the reason it doesn't work properly.
The fundamental problem is that the various things involved in
refreshing or updating the list of pools would aggressively ignore,
remove, skip or fail on pools that stop existing, or that already exist.
Mostly this meant that once a pool is removed from the list, it will
never be seen again. Restoring pool_list_update() to the
zpool_do_iostat() loop only partially fixes this - it would find "new"
pools again, but only in the "all pools" (no args) mode, and because its
iterator callback add_pool() would abort the iterator if it already has
a pool listed, it would only add pools if there weren't any already.
So, this commit reworks the structure somewhat. pool_list_update()
becomes pool_list_refresh(), and will ensure the state of all pools in
the list are updated. In the "all pools" mode, it will also add new
pools and remove pools that disappear, but when a fixed list of pools is
used, the list doesn't change, only the state of the pools within it.
The rest of the commit is adjusting things for this much simpler
structure. Regardless of the mode in use, pool_list_refresh() will
always do the right thing, so the driver code can just get on with the
display.
Now that pools can appear and disappear, I've made it so the header (if
enabled) is re-printed when the list changes, so that its easier to see
what's happening if the column widths change.
Since this is all rather complicated, I've included tests for the "all
pools" and "set of pools" modes.
Sponsored-by: Klara, Inc.
Sponsored-by: Wasabi Technology, Inc.
Reviewed-by: Tony Hutter <hutter2@llnl.gov>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Igor Kozhukhov <igor@dilos.org>
Signed-off-by: Rob Norris <rob.norris@klarasystems.com>
Closes #17786
This commit is contained in:
committed by
Brian Behlendorf
parent
abda34b1c0
commit
35ec4b14ab
@@ -491,6 +491,10 @@ tests = ['zpool_import_001_pos', 'zpool_import_002_pos',
|
||||
tags = ['functional', 'cli_root', 'zpool_import']
|
||||
timeout = 1200
|
||||
|
||||
[tests/functional/cli_root/zpool_iostat]
|
||||
tests = ['zpool_iostat_interval_all', 'zpool_iostat_interval_some']
|
||||
tags = ['functional', 'cli_root', 'zpool_iostat']
|
||||
|
||||
[tests/functional/cli_root/zpool_labelclear]
|
||||
tests = ['zpool_labelclear_active', 'zpool_labelclear_exported',
|
||||
'zpool_labelclear_removed', 'zpool_labelclear_valid']
|
||||
|
||||
@@ -197,6 +197,7 @@ nobase_dist_datadir_zfs_tests_tests_DATA += \
|
||||
functional/cli_root/zpool_import/blockfiles/unclean_export.dat.bz2 \
|
||||
functional/cli_root/zpool_import/zpool_import.cfg \
|
||||
functional/cli_root/zpool_import/zpool_import.kshlib \
|
||||
functional/cli_root/zpool_iostat/zpool_iostat.kshlib \
|
||||
functional/cli_root/zpool_initialize/zpool_initialize.kshlib \
|
||||
functional/cli_root/zpool_labelclear/labelclear.cfg \
|
||||
functional/cli_root/zpool_remove/zpool_remove.cfg \
|
||||
@@ -1181,6 +1182,10 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
|
||||
functional/cli_root/zpool_import/zpool_import_parallel_admin.ksh \
|
||||
functional/cli_root/zpool_import/zpool_import_parallel_neg.ksh \
|
||||
functional/cli_root/zpool_import/zpool_import_parallel_pos.ksh \
|
||||
functional/cli_root/zpool_iostat/setup.ksh \
|
||||
functional/cli_root/zpool_iostat/cleanup.ksh \
|
||||
functional/cli_root/zpool_iostat/zpool_iostat_interval_all.ksh \
|
||||
functional/cli_root/zpool_iostat/zpool_iostat_interval_some.ksh \
|
||||
functional/cli_root/zpool_initialize/cleanup.ksh \
|
||||
functional/cli_root/zpool_initialize/zpool_initialize_attach_detach_add_remove.ksh \
|
||||
functional/cli_root/zpool_initialize/zpool_initialize_fault_export_import_online.ksh \
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/bin/ksh -p
|
||||
# SPDX-License-Identifier: CDDL-1.0
|
||||
#
|
||||
# CDDL HEADER START
|
||||
#
|
||||
# The contents of this file are subject to the terms of the
|
||||
# Common Development and Distribution License (the "License").
|
||||
# You may not use this file except in compliance with the License.
|
||||
#
|
||||
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
# or https://opensource.org/licenses/CDDL-1.0.
|
||||
# See the License for the specific language governing permissions
|
||||
# and limitations under the License.
|
||||
#
|
||||
# When distributing Covered Code, include this CDDL HEADER in each
|
||||
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
# If applicable, add the following below this CDDL HEADER, with the
|
||||
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
#
|
||||
# CDDL HEADER END
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2025, Klara, Inc.
|
||||
#
|
||||
#
|
||||
. $STF_SUITE/include/libtest.shlib
|
||||
|
||||
log_pass
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/bin/ksh -p
|
||||
# SPDX-License-Identifier: CDDL-1.0
|
||||
#
|
||||
# CDDL HEADER START
|
||||
#
|
||||
# The contents of this file are subject to the terms of the
|
||||
# Common Development and Distribution License (the "License").
|
||||
# You may not use this file except in compliance with the License.
|
||||
#
|
||||
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
# or https://opensource.org/licenses/CDDL-1.0.
|
||||
# See the License for the specific language governing permissions
|
||||
# and limitations under the License.
|
||||
#
|
||||
# When distributing Covered Code, include this CDDL HEADER in each
|
||||
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
# If applicable, add the following below this CDDL HEADER, with the
|
||||
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
#
|
||||
# CDDL HEADER END
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2025, Klara, Inc.
|
||||
#
|
||||
#
|
||||
. $STF_SUITE/include/libtest.shlib
|
||||
|
||||
verify_runnable "global"
|
||||
|
||||
log_pass
|
||||
@@ -0,0 +1,235 @@
|
||||
# SPDX-License-Identifier: CDDL-1.0
|
||||
#
|
||||
# CDDL HEADER START
|
||||
#
|
||||
# The contents of this file are subject to the terms of the
|
||||
# Common Development and Distribution License (the "License").
|
||||
# You may not use this file except in compliance with the License.
|
||||
#
|
||||
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
# or https://opensource.org/licenses/CDDL-1.0.
|
||||
# See the License for the specific language governing permissions
|
||||
# and limitations under the License.
|
||||
#
|
||||
# When distributing Covered Code, include this CDDL HEADER in each
|
||||
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
# If applicable, add the following below this CDDL HEADER, with the
|
||||
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
#
|
||||
# CDDL HEADER END
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2025, Klara, Inc.
|
||||
#
|
||||
|
||||
# Since we want to make sure that iostat responds correctly as pools appear and
|
||||
# disappear, we run it in the background and capture its output to a file.
|
||||
# Once we're done, we parse the output and ensure it matches what we'd expect
|
||||
# from the operations we performed.
|
||||
#
|
||||
# Because iostat is producing output every interval, it may produce the "same"
|
||||
# output for each step of the change; in fact, we want that to make sure we
|
||||
# don't miss anything. So, we describe what we expect as a series of "chunks".
|
||||
# Each chunk is a particular kind of output, which may repeat. Current known
|
||||
# chunk types are:
|
||||
#
|
||||
# NOPOOL: the text "no pools available"
|
||||
# HEADER: three lines, starting with "capacity", "pool" and "----" respectively.
|
||||
# (the rough shape of the normal iostat header).
|
||||
# POOL1: a line starting with "pool1" (stats line for a pool of that name)
|
||||
# POOL2: a line starting with "pool2"
|
||||
# POOLBOTH: three lines, starting with "pool1", "pool2" (either order) and
|
||||
# "-----" respectively. (the pool stat output for multiple pools)
|
||||
#
|
||||
# (the parser may produce other chunks in a failed parse to assist with
|
||||
# debugging, but they should never be part of the "wanted" output See the
|
||||
# parser commentary below).
|
||||
#
|
||||
# To help recognise the start of a new interval output, we run iostat with the
|
||||
# -T u option, which will output a numeric timestamp before each header or
|
||||
# second-or-later pool stat after the header.
|
||||
#
|
||||
# To keep the test run shorter, we use a subsecond interval, but to make sure
|
||||
# nothing is missed, we sleep for three intervals after each change.
|
||||
|
||||
typeset _iostat_out=$(mktemp)
|
||||
typeset _iostat_pid=""
|
||||
|
||||
function cleanup_iostat {
|
||||
if [[ -n $_iostat_pid ]] ; then
|
||||
kill -KILL $_iostat_pid || true
|
||||
fi
|
||||
rm -f $_iostat_out
|
||||
}
|
||||
|
||||
function start_iostat {
|
||||
zpool iostat -T u $@ 0.1 > $_iostat_out 2>&1 &
|
||||
_iostat_pid=$!
|
||||
}
|
||||
|
||||
function stop_iostat {
|
||||
kill -TERM $_iostat_pid
|
||||
wait $_iostat_pid
|
||||
_iostat_pid=""
|
||||
}
|
||||
|
||||
function delay_iostat {
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
typeset -a _iostat_expect
|
||||
function expect_iostat {
|
||||
typeset chunk=$1
|
||||
_iostat_expect+=($chunk)
|
||||
}
|
||||
|
||||
# Parse the output The `state` var is used to track state across
|
||||
# multiple lines. The `last` var and the `_got_iostat` function are used
|
||||
# to record the completed chunks, and to collapse repetitions.
|
||||
typeset -a _iostat_got
|
||||
typeset _iostat_last=""
|
||||
typeset _iostat_state=""
|
||||
|
||||
function _got_iostat {
|
||||
typeset chunk=$1
|
||||
if [[ -n $chunk && $_iostat_last != $chunk ]] ; then
|
||||
_iostat_last=$chunk
|
||||
_iostat_got+=($chunk)
|
||||
fi
|
||||
_iostat_state=""
|
||||
}
|
||||
|
||||
function verify_iostat {
|
||||
|
||||
cat $_iostat_out | while read line ; do
|
||||
|
||||
# The "no pools available" text has no timestamp or other
|
||||
# header, and should never appear in the middle of multiline
|
||||
# chunk, so we can close any in-flight state.
|
||||
if [[ $line = "no pools available" ]] ; then
|
||||
_got_iostat $_iostat_state
|
||||
_got_iostat "NOPOOL"
|
||||
continue
|
||||
fi
|
||||
|
||||
# A run of digits alone on the line is a timestamp (the `-T u`
|
||||
# switch to `iostat`). It closes any in-flight state as a
|
||||
# complete chunk, and indicates the start of a new chunk.
|
||||
if [[ -z ${line/#+([0-9])/} ]] ; then
|
||||
_got_iostat $_iostat_state
|
||||
_iostat_state="TIMESTAMP"
|
||||
continue
|
||||
fi
|
||||
|
||||
# For this test, the first word of each line should be unique,
|
||||
# so we extract it and use it for simplicity.
|
||||
typeset first=${line%% *}
|
||||
|
||||
# Header is emitted whenever the pool list changes. It has
|
||||
# three lines:
|
||||
#
|
||||
# capacity operations bandwidth
|
||||
# pool alloc free read write read write
|
||||
# ---------- ----- ----- ----- ----- ----- -----
|
||||
#
|
||||
# Each line moves the state; when we get to a run of dashes, we
|
||||
# commit. Note that we check for one-or-more dashes, because
|
||||
# the width can vary depending on the length of pool name.
|
||||
#
|
||||
if [[ $_iostat_state = "TIMESTAMP" &&
|
||||
$first = "capacity" ]] ; then
|
||||
_iostat_state="INHEADER1"
|
||||
continue
|
||||
fi
|
||||
if [[ $_iostat_state = "INHEADER1" &&
|
||||
$first = "pool" ]] ; then
|
||||
_iostat_state="INHEADER2"
|
||||
continue
|
||||
fi
|
||||
if [[ $_iostat_state = "INHEADER2" &&
|
||||
-z ${first/#+(-)/} ]] ; then
|
||||
# Headers never repeat, so if the last committed chunk
|
||||
# was a header, we commit this one as EXTRAHEADER so we
|
||||
# can see it in the error output.
|
||||
if [[ $_iostat_last = "HEADER" ]] ; then
|
||||
_got_iostat "EXTRAHEADER"
|
||||
elif [[ $_iostat_last != "EXTRAHEADER" ]] ; then
|
||||
_got_iostat "HEADER"
|
||||
fi
|
||||
_iostat_state="HEADER"
|
||||
continue
|
||||
fi
|
||||
|
||||
# A pool stat line looks like:
|
||||
#
|
||||
# pool1 147K 240M 0 0 0 0
|
||||
#
|
||||
# If there are multiple pools, iostat follows them with a
|
||||
# separator of dashed lines:
|
||||
#
|
||||
# pool1 147K 240M 0 0 0 0
|
||||
# pool2 147K 240M 0 0 0 0
|
||||
# ---------- ----- ----- ----- ----- ----- -----
|
||||
#
|
||||
# Stats rows always start after a timestamp or a header. If the
|
||||
# header was emitted, we won't see a timestamp here (it goes
|
||||
# before the header).
|
||||
#
|
||||
# Because our test exercises both pools on their own and
|
||||
# together, we allow pools in either order. In practice they
|
||||
# are sorted, but that's a side-effect of the implementation
|
||||
# (see zpool_compare()), so we're not going to rely on it here.
|
||||
if [[ $first = "pool1" ]] || [[ $first = "pool2" ]] ; then
|
||||
|
||||
# First line, track which one we saw. If it's a
|
||||
# standalone line, it will be committed by the next
|
||||
# NOPOOL or TIMESTAMP above (or the `_got_iostat` after
|
||||
# the loop if this is the last line).
|
||||
if [[ $_iostat_state == "TIMESTAMP" ||
|
||||
$_iostat_state == "HEADER" ]] ; then
|
||||
if [[ $first = "pool1" ]] ; then
|
||||
_iostat_state="POOL1"
|
||||
elif [[ $first = "pool2" ]] ; then
|
||||
_iostat_state="POOL2"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
# If this is the second pool, we're in a multi-pool
|
||||
# block, and need to look for the separator to close it
|
||||
# out.
|
||||
if [[ $_iostat_state = "POOL1" && $first = "pool2" ]] ||
|
||||
[[ $_iostat_state = "POOL2" && $first = "pool1" ]] ;
|
||||
then
|
||||
_iostat_state="INPOOLBOTH"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Separator after the stats block.
|
||||
if [[ $_iostat_state = "INPOOLBOTH" &&
|
||||
-z ${first/#+(-)/} ]] ; then
|
||||
_got_iostat "POOLBOTH"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Anything else will fall through to here. We commit any
|
||||
# in-flight state, then "UNKNOWN", all to help with debugging..
|
||||
if [[ $_iostat_state != "UNKNOWN" ]] ; then
|
||||
_got_iostat $_iostat_state
|
||||
_got_iostat "UNKNOWN"
|
||||
fi
|
||||
done
|
||||
|
||||
# Close out any remaining state.
|
||||
_got_iostat $_iostat_state
|
||||
|
||||
# Compare what we wanted with what we got, and pass/fail the test!
|
||||
if [[ "${_iostat_expect[*]}" != "${_iostat_got[*]}" ]] ; then
|
||||
log_note "expected: ${_iostat_expect[*]}"
|
||||
log_note " got: ${_iostat_got[*]}"
|
||||
log_fail "zpool iostat did not produce expected output"
|
||||
fi
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
#!/bin/ksh -p
|
||||
# SPDX-License-Identifier: CDDL-1.0
|
||||
#
|
||||
# CDDL HEADER START
|
||||
#
|
||||
# The contents of this file are subject to the terms of the
|
||||
# Common Development and Distribution License (the "License").
|
||||
# You may not use this file except in compliance with the License.
|
||||
#
|
||||
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
# or https://opensource.org/licenses/CDDL-1.0.
|
||||
# See the License for the specific language governing permissions
|
||||
# and limitations under the License.
|
||||
#
|
||||
# When distributing Covered Code, include this CDDL HEADER in each
|
||||
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
# If applicable, add the following below this CDDL HEADER, with the
|
||||
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
#
|
||||
# CDDL HEADER END
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2025, Klara, Inc.
|
||||
#
|
||||
|
||||
# `zpool iostat <N>` should keep running and update the pools it displays as
|
||||
# pools are created/destroyed/imported/export.
|
||||
|
||||
. $STF_SUITE/include/libtest.shlib
|
||||
. $STF_SUITE/tests/functional/cli_root/zpool_iostat/zpool_iostat.kshlib
|
||||
|
||||
typeset vdev1=$(mktemp)
|
||||
typeset vdev2=$(mktemp)
|
||||
|
||||
function cleanup {
|
||||
cleanup_iostat
|
||||
|
||||
poolexists pool1 && destroy_pool pool1
|
||||
poolexists pool2 && destroy_pool pool2
|
||||
rm -f $vdev1 $vdev2
|
||||
}
|
||||
|
||||
log_must mkfile $MINVDEVSIZE $vdev1 $vdev2
|
||||
|
||||
expect_iostat "NOPOOL"
|
||||
|
||||
start_iostat
|
||||
|
||||
delay_iostat
|
||||
|
||||
expect_iostat "HEADER"
|
||||
expect_iostat "POOL1"
|
||||
log_must zpool create pool1 $vdev1
|
||||
delay_iostat
|
||||
|
||||
expect_iostat "HEADER"
|
||||
expect_iostat "POOLBOTH"
|
||||
log_must zpool create pool2 $vdev2
|
||||
delay_iostat
|
||||
|
||||
expect_iostat "NOPOOL"
|
||||
log_must zpool export -a
|
||||
delay_iostat
|
||||
|
||||
expect_iostat "HEADER"
|
||||
expect_iostat "POOL2"
|
||||
log_must zpool import -d $vdev2 pool2
|
||||
delay_iostat
|
||||
|
||||
expect_iostat "HEADER"
|
||||
expect_iostat "POOLBOTH"
|
||||
log_must zpool import -d $vdev1 pool1
|
||||
delay_iostat
|
||||
|
||||
expect_iostat "HEADER"
|
||||
expect_iostat "POOL2"
|
||||
log_must zpool destroy pool1
|
||||
delay_iostat
|
||||
|
||||
expect_iostat "NOPOOL"
|
||||
log_must zpool destroy pool2
|
||||
delay_iostat
|
||||
|
||||
stop_iostat
|
||||
|
||||
verify_iostat
|
||||
|
||||
log_pass "zpool iostat in interval mode follows pool updates"
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
#!/bin/ksh -p
|
||||
# SPDX-License-Identifier: CDDL-1.0
|
||||
#
|
||||
# CDDL HEADER START
|
||||
#
|
||||
# The contents of this file are subject to the terms of the
|
||||
# Common Development and Distribution License (the "License").
|
||||
# You may not use this file except in compliance with the License.
|
||||
#
|
||||
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
||||
# or https://opensource.org/licenses/CDDL-1.0.
|
||||
# See the License for the specific language governing permissions
|
||||
# and limitations under the License.
|
||||
#
|
||||
# When distributing Covered Code, include this CDDL HEADER in each
|
||||
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
||||
# If applicable, add the following below this CDDL HEADER, with the
|
||||
# fields enclosed by brackets "[]" replaced with your own identifying
|
||||
# information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
#
|
||||
# CDDL HEADER END
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2025, Klara, Inc.
|
||||
#
|
||||
|
||||
# `zpool iostat <pools> <N>` should keep running and only show the listed pools.
|
||||
|
||||
. $STF_SUITE/include/libtest.shlib
|
||||
. $STF_SUITE/tests/functional/cli_root/zpool_iostat/zpool_iostat.kshlib
|
||||
|
||||
typeset vdev1=$(mktemp)
|
||||
typeset vdev2=$(mktemp)
|
||||
|
||||
function cleanup {
|
||||
cleanup_iostat
|
||||
|
||||
poolexists pool1 && destroy_pool pool1
|
||||
poolexists pool2 && destroy_pool pool2
|
||||
rm -f $vdev1 $vdev2
|
||||
}
|
||||
|
||||
log_must mkfile $MINVDEVSIZE $vdev1 $vdev2
|
||||
|
||||
log_must zpool create pool1 $vdev1
|
||||
delay_iostat
|
||||
|
||||
expect_iostat "HEADER"
|
||||
expect_iostat "POOL1"
|
||||
start_iostat pool1
|
||||
delay_iostat
|
||||
|
||||
log_must zpool create pool2 $vdev2
|
||||
delay_iostat
|
||||
|
||||
expect_iostat "NOPOOL"
|
||||
log_must zpool export -a
|
||||
delay_iostat
|
||||
|
||||
log_must zpool import -d $vdev2 pool2
|
||||
delay_iostat
|
||||
|
||||
expect_iostat "HEADER"
|
||||
expect_iostat "POOL1"
|
||||
log_must zpool import -d $vdev1 pool1
|
||||
delay_iostat
|
||||
|
||||
expect_iostat "NOPOOL"
|
||||
log_must zpool destroy pool1
|
||||
delay_iostat
|
||||
|
||||
log_must zpool destroy pool2
|
||||
delay_iostat
|
||||
|
||||
stop_iostat
|
||||
|
||||
verify_iostat
|
||||
|
||||
log_pass "zpool iostat in interval mode with pools follows listed pool updates"
|
||||
Reference in New Issue
Block a user