Skip to content

Commit f0a95e8

Browse files
authored
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. Reviewed-by: Tony Hutter <[email protected]> Reviewed-by: Brian Behlendorf <[email protected]> Reviewed-by: Igor Kozhukhov <[email protected]> Signed-off-by: Rob Norris <[email protected]> Closes #17786
1 parent 75be5f2 commit f0a95e8

File tree

10 files changed

+588
-70
lines changed

10 files changed

+588
-70
lines changed

cmd/zpool/zpool_iter.c

Lines changed: 90 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
/*
2828
* Copyright 2016 Igor Kozhukhov <[email protected]>.
29+
* Copyright (c) 2025, Klara, Inc.
2930
*/
3031

3132
#include <libintl.h>
@@ -52,7 +53,7 @@
5253
typedef struct zpool_node {
5354
zpool_handle_t *zn_handle;
5455
uu_avl_node_t zn_avlnode;
55-
int zn_mark;
56+
hrtime_t zn_last_refresh;
5657
} zpool_node_t;
5758

5859
struct zpool_list {
@@ -62,6 +63,7 @@ struct zpool_list {
6263
uu_avl_pool_t *zl_pool;
6364
zprop_list_t **zl_proplist;
6465
zfs_type_t zl_type;
66+
hrtime_t zl_last_refresh;
6567
};
6668

6769
static int
@@ -81,32 +83,47 @@ zpool_compare(const void *larg, const void *rarg, void *unused)
8183
* of known pools.
8284
*/
8385
static int
84-
add_pool(zpool_handle_t *zhp, void *data)
86+
add_pool(zpool_handle_t *zhp, zpool_list_t *zlp)
8587
{
86-
zpool_list_t *zlp = data;
87-
zpool_node_t *node = safe_malloc(sizeof (zpool_node_t));
88+
zpool_node_t *node, *new = safe_malloc(sizeof (zpool_node_t));
8889
uu_avl_index_t idx;
8990

90-
node->zn_handle = zhp;
91-
uu_avl_node_init(node, &node->zn_avlnode, zlp->zl_pool);
92-
if (uu_avl_find(zlp->zl_avl, node, NULL, &idx) == NULL) {
91+
new->zn_handle = zhp;
92+
uu_avl_node_init(new, &new->zn_avlnode, zlp->zl_pool);
93+
94+
node = uu_avl_find(zlp->zl_avl, new, NULL, &idx);
95+
if (node == NULL) {
9396
if (zlp->zl_proplist &&
9497
zpool_expand_proplist(zhp, zlp->zl_proplist,
9598
zlp->zl_type, zlp->zl_literal) != 0) {
9699
zpool_close(zhp);
97-
free(node);
100+
free(new);
98101
return (-1);
99102
}
100-
uu_avl_insert(zlp->zl_avl, node, idx);
103+
new->zn_last_refresh = zlp->zl_last_refresh;
104+
uu_avl_insert(zlp->zl_avl, new, idx);
101105
} else {
106+
node->zn_last_refresh = zlp->zl_last_refresh;
102107
zpool_close(zhp);
103-
free(node);
108+
free(new);
104109
return (-1);
105110
}
106111

107112
return (0);
108113
}
109114

115+
/*
116+
* add_pool(), but always returns 0. This allows zpool_iter() to continue
117+
* even if a pool exists in the tree, or we fail to get the properties for
118+
* a new one.
119+
*/
120+
static int
121+
add_pool_cb(zpool_handle_t *zhp, void *data)
122+
{
123+
(void) add_pool(zhp, data);
124+
return (0);
125+
}
126+
110127
/*
111128
* Create a list of pools based on the given arguments. If we're given no
112129
* arguments, then iterate over all pools in the system and add them to the AVL
@@ -135,9 +152,10 @@ pool_list_get(int argc, char **argv, zprop_list_t **proplist, zfs_type_t type,
135152
zlp->zl_type = type;
136153

137154
zlp->zl_literal = literal;
155+
zlp->zl_last_refresh = gethrtime();
138156

139157
if (argc == 0) {
140-
(void) zpool_iter(g_zfs, add_pool, zlp);
158+
(void) zpool_iter(g_zfs, add_pool_cb, zlp);
141159
zlp->zl_findall = B_TRUE;
142160
} else {
143161
int i;
@@ -159,15 +177,69 @@ pool_list_get(int argc, char **argv, zprop_list_t **proplist, zfs_type_t type,
159177
}
160178

161179
/*
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.
180+
* Refresh the state of all pools on the list. Additionally, if no options were
181+
* given on the command line, add any new pools and remove any that are no
182+
* longer available.
165183
*/
166-
void
167-
pool_list_update(zpool_list_t *zlp)
184+
int
185+
pool_list_refresh(zpool_list_t *zlp)
168186
{
169-
if (zlp->zl_findall)
170-
(void) zpool_iter(g_zfs, add_pool, zlp);
187+
zlp->zl_last_refresh = gethrtime();
188+
189+
if (!zlp->zl_findall) {
190+
/*
191+
* This list is a fixed list of pools, so we must not add
192+
* or remove any. Just walk over them and refresh their
193+
* state.
194+
*/
195+
int navail = 0;
196+
for (zpool_node_t *node = uu_avl_first(zlp->zl_avl);
197+
node != NULL; node = uu_avl_next(zlp->zl_avl, node)) {
198+
boolean_t missing;
199+
zpool_refresh_stats(node->zn_handle, &missing);
200+
navail += !missing;
201+
node->zn_last_refresh = zlp->zl_last_refresh;
202+
}
203+
return (navail);
204+
}
205+
206+
/*
207+
* Search for any new pools and add them to the list. zpool_iter()
208+
* will call zpool_refresh_stats() as part of its work, so this has
209+
* the side effect of updating all active handles.
210+
*/
211+
(void) zpool_iter(g_zfs, add_pool_cb, zlp);
212+
213+
/*
214+
* Walk the list for any that weren't refreshed, and update and remove
215+
* them. It's not enough to just skip available ones, as zpool_iter()
216+
* won't update them, so they'll still appear active in our list.
217+
*/
218+
zpool_node_t *node, *next;
219+
for (node = uu_avl_first(zlp->zl_avl); node != NULL; node = next) {
220+
next = uu_avl_next(zlp->zl_avl, node);
221+
222+
/*
223+
* Skip any that were refreshed and are online; they're already
224+
* handled.
225+
*/
226+
if (node->zn_last_refresh == zlp->zl_last_refresh &&
227+
zpool_get_state(node->zn_handle) != POOL_STATE_UNAVAIL)
228+
continue;
229+
230+
/* Do the refresh ourselves, just in case. */
231+
boolean_t missing;
232+
zpool_refresh_stats(node->zn_handle, &missing);
233+
if (missing) {
234+
uu_avl_remove(zlp->zl_avl, node);
235+
zpool_close(node->zn_handle);
236+
free(node);
237+
} else {
238+
node->zn_last_refresh = zlp->zl_last_refresh;
239+
}
240+
}
241+
242+
return (uu_avl_numnodes(zlp->zl_avl));
171243
}
172244

173245
/*
@@ -190,23 +262,6 @@ pool_list_iter(zpool_list_t *zlp, int unavail, zpool_iter_f func,
190262
return (ret);
191263
}
192264

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-
210265
/*
211266
* Free all the handles associated with this list.
212267
*/

cmd/zpool/zpool_main.c

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* Copyright (c) 2017, Intel Corporation.
3434
* Copyright (c) 2019, loli10K <[email protected]>
3535
* Copyright (c) 2021, Colm Buckley <[email protected]>
36-
* Copyright (c) 2021, 2023, Klara Inc.
36+
* Copyright (c) 2021, 2023, 2025, Klara, Inc.
3737
* Copyright (c) 2021, 2025 Hewlett Packard Enterprise Development LP.
3838
*/
3939

@@ -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
@@ -491,6 +491,10 @@ tests = ['zpool_import_001_pos', 'zpool_import_002_pos',
491491
tags = ['functional', 'cli_root', 'zpool_import']
492492
timeout = 1200
493493

494+
[tests/functional/cli_root/zpool_iostat]
495+
tests = ['zpool_iostat_interval_all', 'zpool_iostat_interval_some']
496+
tags = ['functional', 'cli_root', 'zpool_iostat']
497+
494498
[tests/functional/cli_root/zpool_labelclear]
495499
tests = ['zpool_labelclear_active', 'zpool_labelclear_exported',
496500
'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 \
@@ -1181,6 +1182,10 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
11811182
functional/cli_root/zpool_import/zpool_import_parallel_admin.ksh \
11821183
functional/cli_root/zpool_import/zpool_import_parallel_neg.ksh \
11831184
functional/cli_root/zpool_import/zpool_import_parallel_pos.ksh \
1185+
functional/cli_root/zpool_iostat/setup.ksh \
1186+
functional/cli_root/zpool_iostat/cleanup.ksh \
1187+
functional/cli_root/zpool_iostat/zpool_iostat_interval_all.ksh \
1188+
functional/cli_root/zpool_iostat/zpool_iostat_interval_some.ksh \
11841189
functional/cli_root/zpool_initialize/cleanup.ksh \
11851190
functional/cli_root/zpool_initialize/zpool_initialize_attach_detach_add_remove.ksh \
11861191
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

0 commit comments

Comments
 (0)