Skip to content

Commit c6c5af8

Browse files
committed
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 3e43edd, 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. Signed-off-by: Rob Norris <[email protected]>
1 parent b2196fb commit c6c5af8

File tree

10 files changed

+554
-61
lines changed

10 files changed

+554
-61
lines changed

cmd/zpool/zpool_iter.c

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,8 @@ zpool_compare(const void *larg, const void *rarg, void *unused)
8181
* of known pools.
8282
*/
8383
static int
84-
add_pool(zpool_handle_t *zhp, void *data)
84+
add_pool(zpool_handle_t *zhp, zpool_list_t *zlp)
8585
{
86-
zpool_list_t *zlp = data;
8786
zpool_node_t *node = safe_malloc(sizeof (zpool_node_t));
8887
uu_avl_index_t idx;
8988

@@ -107,6 +106,18 @@ add_pool(zpool_handle_t *zhp, void *data)
107106
return (0);
108107
}
109108

109+
/*
110+
* add_pool(), but always returns 0. This allows zpool_iter() to continue
111+
* even if a pool exists in the tree, or we fail to get the properties for
112+
* a new one.
113+
*/
114+
static int
115+
add_pool_cb(zpool_handle_t *zhp, void *data)
116+
{
117+
(void) add_pool(zhp, data);
118+
return (0);
119+
}
120+
110121
/*
111122
* Create a list of pools based on the given arguments. If we're given no
112123
* arguments, then iterate over all pools in the system and add them to the AVL
@@ -137,7 +148,7 @@ pool_list_get(int argc, char **argv, zprop_list_t **proplist, zfs_type_t type,
137148
zlp->zl_literal = literal;
138149

139150
if (argc == 0) {
140-
(void) zpool_iter(g_zfs, add_pool, zlp);
151+
(void) zpool_iter(g_zfs, add_pool_cb, zlp);
141152
zlp->zl_findall = B_TRUE;
142153
} else {
143154
int i;
@@ -159,15 +170,51 @@ pool_list_get(int argc, char **argv, zprop_list_t **proplist, zfs_type_t type,
159170
}
160171

161172
/*
162-
* Search for any new pools, adding them to the list. We only add pools when no
163-
* options were given on the command line. Otherwise, we keep the list fixed as
164-
* those that were explicitly specified.
173+
* Refresh the state of all pools on the list. Additionally, if no options were
174+
* given on the command line, add any new pools and remove any that are no
175+
* longer available.
165176
*/
166-
void
167-
pool_list_update(zpool_list_t *zlp)
177+
int
178+
pool_list_refresh(zpool_list_t *zlp)
168179
{
169-
if (zlp->zl_findall)
170-
(void) zpool_iter(g_zfs, add_pool, zlp);
180+
if (!zlp->zl_findall) {
181+
/*
182+
* This list is a fixed list of pools, so we must not add
183+
* or remove any. Just walk over them and refresh their
184+
* state.
185+
*/
186+
int navail = 0;
187+
for (zpool_node_t *node = uu_avl_first(zlp->zl_avl);
188+
node != NULL; node = uu_avl_next(zlp->zl_avl, node)) {
189+
boolean_t missing;
190+
zpool_refresh_stats(node->zn_handle, &missing);
191+
navail += !missing;
192+
}
193+
return (navail);
194+
}
195+
196+
/*
197+
* Search for any new pools and add them to the list. zpool_iter()
198+
* will call zpool_refresh_stats() as part of its work, so this has
199+
* the side effect of updating all active handles.
200+
*/
201+
(void) zpool_iter(g_zfs, add_pool_cb, zlp);
202+
203+
/* Now walk the list and remove any that are no longer available. */
204+
zpool_node_t *node, *next;
205+
for (node = uu_avl_first(zlp->zl_avl); node != NULL; node = next) {
206+
next = uu_avl_next(zlp->zl_avl, node);
207+
208+
boolean_t missing;
209+
zpool_refresh_stats(node->zn_handle, &missing);
210+
if (missing) {
211+
uu_avl_remove(zlp->zl_avl, node);
212+
zpool_close(node->zn_handle);
213+
free(node);
214+
}
215+
}
216+
217+
return (uu_avl_numnodes(zlp->zl_avl));
171218
}
172219

173220
/*
@@ -190,23 +237,6 @@ pool_list_iter(zpool_list_t *zlp, int unavail, zpool_iter_f func,
190237
return (ret);
191238
}
192239

193-
/*
194-
* Remove the given pool from the list. When running iostat, we want to remove
195-
* those pools that no longer exist.
196-
*/
197-
void
198-
pool_list_remove(zpool_list_t *zlp, zpool_handle_t *zhp)
199-
{
200-
zpool_node_t search, *node;
201-
202-
search.zn_handle = zhp;
203-
if ((node = uu_avl_find(zlp->zl_avl, &search, NULL, NULL)) != NULL) {
204-
uu_avl_remove(zlp->zl_avl, node);
205-
zpool_close(node->zn_handle);
206-
free(node);
207-
}
208-
}
209-
210240
/*
211241
* Free all the handles associated with this list.
212242
*/

cmd/zpool/zpool_main.c

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5761,24 +5761,6 @@ print_vdev_stats(zpool_handle_t *zhp, const char *name, nvlist_t *oldnv,
57615761
return (ret);
57625762
}
57635763

5764-
static int
5765-
refresh_iostat(zpool_handle_t *zhp, void *data)
5766-
{
5767-
iostat_cbdata_t *cb = data;
5768-
boolean_t missing;
5769-
5770-
/*
5771-
* If the pool has disappeared, remove it from the list and continue.
5772-
*/
5773-
if (zpool_refresh_stats(zhp, &missing) != 0)
5774-
return (-1);
5775-
5776-
if (missing)
5777-
pool_list_remove(cb->cb_list, zhp);
5778-
5779-
return (0);
5780-
}
5781-
57825764
/*
57835765
* Callback to print out the iostats for the given pool.
57845766
*/
@@ -6359,15 +6341,14 @@ get_namewidth_iostat(zpool_handle_t *zhp, void *data)
63596341
* This command can be tricky because we want to be able to deal with pool
63606342
* creation/destruction as well as vdev configuration changes. The bulk of this
63616343
* processing is handled by the pool_list_* routines in zpool_iter.c. We rely
6362-
* on pool_list_update() to detect the addition of new pools. Configuration
6363-
* changes are all handled within libzfs.
6344+
* on pool_list_refresh() to detect the addition and removal of pools.
6345+
* Configuration changes are all handled within libzfs.
63646346
*/
63656347
int
63666348
zpool_do_iostat(int argc, char **argv)
63676349
{
63686350
int c;
63696351
int ret;
6370-
int npools;
63716352
float interval = 0;
63726353
unsigned long count = 0;
63736354
zpool_list_t *list;
@@ -6618,26 +6599,31 @@ zpool_do_iostat(int argc, char **argv)
66186599
return (1);
66196600
}
66206601

6602+
int last_npools = 0;
66216603
for (;;) {
6622-
if ((npools = pool_list_count(list)) == 0)
6604+
/*
6605+
* Refresh all pools in list, adding or removing pools as
6606+
* necessary.
6607+
*/
6608+
int npools = pool_list_refresh(list);
6609+
if (npools == 0) {
66236610
(void) fprintf(stderr, gettext("no pools available\n"));
6624-
else {
6611+
} else {
6612+
/*
6613+
* If the list of pools has changed since last time
6614+
* around, reset the iteration count to force the
6615+
* header to be redisplayed.
6616+
*/
6617+
if (last_npools != npools)
6618+
cb.cb_iteration = 0;
6619+
66256620
/*
66266621
* If this is the first iteration and -y was supplied
66276622
* we skip any printing.
66286623
*/
66296624
boolean_t skip = (omit_since_boot &&
66306625
cb.cb_iteration == 0);
66316626

6632-
/*
6633-
* Refresh all statistics. This is done as an
6634-
* explicit step before calculating the maximum name
6635-
* width, so that any * configuration changes are
6636-
* properly accounted for.
6637-
*/
6638-
(void) pool_list_iter(list, B_FALSE, refresh_iostat,
6639-
&cb);
6640-
66416627
/*
66426628
* Iterate over all pools to determine the maximum width
66436629
* for the pool / device name column across all pools.
@@ -6728,6 +6714,8 @@ zpool_do_iostat(int argc, char **argv)
67286714

67296715
(void) fflush(stdout);
67306716
(void) fsleep(interval);
6717+
6718+
last_npools = npools;
67316719
}
67326720

67336721
pool_list_free(list);

cmd/zpool/zpool_util.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,10 @@ typedef struct zpool_list zpool_list_t;
7676

7777
zpool_list_t *pool_list_get(int, char **, zprop_list_t **, zfs_type_t,
7878
boolean_t, int *);
79-
void pool_list_update(zpool_list_t *);
79+
int pool_list_refresh(zpool_list_t *);
8080
int pool_list_iter(zpool_list_t *, int unavail, zpool_iter_f, void *);
8181
void pool_list_free(zpool_list_t *);
8282
int pool_list_count(zpool_list_t *);
83-
void pool_list_remove(zpool_list_t *, zpool_handle_t *);
8483

8584
extern libzfs_handle_t *g_zfs;
8685

tests/runfiles/common.run

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,10 @@ tests = ['zpool_import_001_pos', 'zpool_import_002_pos',
490490
tags = ['functional', 'cli_root', 'zpool_import']
491491
timeout = 1200
492492

493+
[tests/functional/cli_root/zpool_iostat]
494+
tests = ['zpool_iostat_interval_all', 'zpool_iostat_interval_some']
495+
tags = ['functional', 'cli_root', 'zpool_iostat']
496+
493497
[tests/functional/cli_root/zpool_labelclear]
494498
tests = ['zpool_labelclear_active', 'zpool_labelclear_exported',
495499
'zpool_labelclear_removed', 'zpool_labelclear_valid']

tests/zfs-tests/tests/Makefile.am

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ nobase_dist_datadir_zfs_tests_tests_DATA += \
197197
functional/cli_root/zpool_import/blockfiles/unclean_export.dat.bz2 \
198198
functional/cli_root/zpool_import/zpool_import.cfg \
199199
functional/cli_root/zpool_import/zpool_import.kshlib \
200+
functional/cli_root/zpool_iostat/zpool_iostat.kshlib \
200201
functional/cli_root/zpool_initialize/zpool_initialize.kshlib \
201202
functional/cli_root/zpool_labelclear/labelclear.cfg \
202203
functional/cli_root/zpool_remove/zpool_remove.cfg \
@@ -1178,6 +1179,10 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
11781179
functional/cli_root/zpool_import/zpool_import_parallel_admin.ksh \
11791180
functional/cli_root/zpool_import/zpool_import_parallel_neg.ksh \
11801181
functional/cli_root/zpool_import/zpool_import_parallel_pos.ksh \
1182+
functional/cli_root/zpool_iostat/setup.ksh \
1183+
functional/cli_root/zpool_iostat/cleanup.ksh \
1184+
functional/cli_root/zpool_iostat/zpool_iostat_interval_all.ksh \
1185+
functional/cli_root/zpool_iostat/zpool_iostat_interval_some.ksh \
11811186
functional/cli_root/zpool_initialize/cleanup.ksh \
11821187
functional/cli_root/zpool_initialize/zpool_initialize_attach_detach_add_remove.ksh \
11831188
functional/cli_root/zpool_initialize/zpool_initialize_fault_export_import_online.ksh \
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/bin/ksh -p
2+
# SPDX-License-Identifier: CDDL-1.0
3+
#
4+
# CDDL HEADER START
5+
#
6+
# The contents of this file are subject to the terms of the
7+
# Common Development and Distribution License (the "License").
8+
# You may not use this file except in compliance with the License.
9+
#
10+
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11+
# or https://opensource.org/licenses/CDDL-1.0.
12+
# See the License for the specific language governing permissions
13+
# and limitations under the License.
14+
#
15+
# When distributing Covered Code, include this CDDL HEADER in each
16+
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17+
# If applicable, add the following below this CDDL HEADER, with the
18+
# fields enclosed by brackets "[]" replaced with your own identifying
19+
# information: Portions Copyright [yyyy] [name of copyright owner]
20+
#
21+
# CDDL HEADER END
22+
#
23+
24+
#
25+
# Copyright (c) 2025 Klara, Inc.
26+
#
27+
#
28+
. $STF_SUITE/include/libtest.shlib
29+
30+
log_pass
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/ksh -p
2+
# SPDX-License-Identifier: CDDL-1.0
3+
#
4+
# CDDL HEADER START
5+
#
6+
# The contents of this file are subject to the terms of the
7+
# Common Development and Distribution License (the "License").
8+
# You may not use this file except in compliance with the License.
9+
#
10+
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11+
# or https://opensource.org/licenses/CDDL-1.0.
12+
# See the License for the specific language governing permissions
13+
# and limitations under the License.
14+
#
15+
# When distributing Covered Code, include this CDDL HEADER in each
16+
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17+
# If applicable, add the following below this CDDL HEADER, with the
18+
# fields enclosed by brackets "[]" replaced with your own identifying
19+
# information: Portions Copyright [yyyy] [name of copyright owner]
20+
#
21+
# CDDL HEADER END
22+
#
23+
24+
#
25+
# Copyright (c) 2025 Klara, Inc.
26+
#
27+
#
28+
. $STF_SUITE/include/libtest.shlib
29+
30+
verify_runnable "global"
31+
32+
log_pass

0 commit comments

Comments
 (0)