diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 39f379cb0..ad25a2f6e 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -6484,7 +6484,8 @@ status_callback(zpool_handle_t *zhp, void *data) nvroot = fnvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE); verify(nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_VDEV_STATS, (uint64_t **)&vs, &c) == 0); - health = zpool_state_to_name(vs->vs_state, vs->vs_aux); + + health = zpool_get_state_str(zhp); (void) printf(gettext(" pool: %s\n"), zpool_get_name(zhp)); (void) printf(gettext(" state: %s\n"), health); diff --git a/configure.ac b/configure.ac index 8e360f46e..b6fca99b4 100644 --- a/configure.ac +++ b/configure.ac @@ -278,6 +278,7 @@ AC_CONFIG_FILES([ tests/zfs-tests/tests/functional/hkdf/Makefile tests/zfs-tests/tests/functional/inheritance/Makefile tests/zfs-tests/tests/functional/inuse/Makefile + tests/zfs-tests/tests/functional/kstat/Makefile tests/zfs-tests/tests/functional/large_files/Makefile tests/zfs-tests/tests/functional/largest_pool/Makefile tests/zfs-tests/tests/functional/link_count/Makefile diff --git a/include/libzfs.h b/include/libzfs.h index 7ea6d7fa0..b98963158 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -301,6 +301,8 @@ int zfs_dev_is_whole_disk(char *dev_name); char *zfs_get_underlying_path(char *dev_name); char *zfs_get_enclosure_sysfs_path(char *dev_name); +const char *zpool_get_state_str(zpool_handle_t *); + /* * Functions to manage pool properties */ diff --git a/include/spl/sys/kstat.h b/include/spl/sys/kstat.h index 9170fe24e..f197ce455 100644 --- a/include/spl/sys/kstat.h +++ b/include/spl/sys/kstat.h @@ -69,10 +69,9 @@ #define KSTAT_FLAG_WRITABLE 0x04 #define KSTAT_FLAG_PERSISTENT 0x08 #define KSTAT_FLAG_DORMANT 0x10 -#define KSTAT_FLAG_UNSUPPORTED \ - (KSTAT_FLAG_VAR_SIZE | KSTAT_FLAG_WRITABLE | \ - KSTAT_FLAG_PERSISTENT | KSTAT_FLAG_DORMANT) - +#define KSTAT_FLAG_INVALID 0x20 +#define KSTAT_FLAG_LONGSTRINGS 0x40 +#define KSTAT_FLAG_NO_HEADERS 0x80 #define KS_MAGIC 0x9d9d9d9d diff --git a/include/sys/spa.h b/include/sys/spa.h index 8a3938e86..e8578be9a 100644 --- a/include/sys/spa.h +++ b/include/sys/spa.h @@ -873,6 +873,7 @@ typedef struct spa_stats { spa_stats_history_t tx_assign_histogram; spa_stats_history_t io_history; spa_stats_history_t mmp_history; + spa_stats_history_t state; /* pool state */ } spa_stats_t; typedef enum txg_state { @@ -1048,6 +1049,8 @@ extern void spa_history_log_internal_ds(struct dsl_dataset *ds, const char *op, extern void spa_history_log_internal_dd(dsl_dir_t *dd, const char *operation, dmu_tx_t *tx, const char *fmt, ...); +extern const char *spa_state_to_name(spa_t *spa); + /* error handling */ struct zbookmark_phys; extern void spa_log_error(spa_t *spa, const zbookmark_phys_t *zb); diff --git a/lib/libspl/include/sys/kstat.h b/lib/libspl/include/sys/kstat.h index 24c71e27c..9bd0d949d 100644 --- a/lib/libspl/include/sys/kstat.h +++ b/lib/libspl/include/sys/kstat.h @@ -304,6 +304,8 @@ typedef struct kstat32 { #define KSTAT_FLAG_PERSISTENT 0x08 #define KSTAT_FLAG_DORMANT 0x10 #define KSTAT_FLAG_INVALID 0x20 +#define KSTAT_FLAG_LONGSTRINGS 0x40 +#define KSTAT_FLAG_NO_HEADERS 0x80 /* * Dynamic update support diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c index 3b16c012f..16b30d177 100644 --- a/lib/libzfs/libzfs_pool.c +++ b/lib/libzfs/libzfs_pool.c @@ -242,6 +242,38 @@ zpool_pool_state_to_name(pool_state_t state) return (gettext("UNKNOWN")); } +/* + * Given a pool handle, return the pool health string ("ONLINE", "DEGRADED", + * "SUSPENDED", etc). + */ +const char * +zpool_get_state_str(zpool_handle_t *zhp) +{ + zpool_errata_t errata; + zpool_status_t status; + nvlist_t *nvroot; + vdev_stat_t *vs; + uint_t vsc; + const char *str; + + status = zpool_get_status(zhp, NULL, &errata); + + if (zpool_get_state(zhp) == POOL_STATE_UNAVAIL) { + str = gettext("FAULTED"); + } else if (status == ZPOOL_STATUS_IO_FAILURE_WAIT || + status == ZPOOL_STATUS_IO_FAILURE_MMP) { + str = gettext("SUSPENDED"); + } else { + verify(nvlist_lookup_nvlist(zpool_get_config(zhp, NULL), + ZPOOL_CONFIG_VDEV_TREE, &nvroot) == 0); + verify(nvlist_lookup_uint64_array(nvroot, + ZPOOL_CONFIG_VDEV_STATS, (uint64_t **)&vs, &vsc) + == 0); + str = zpool_state_to_name(vs->vs_state, vs->vs_aux); + } + return (str); +} + /* * Get a zpool property value for 'prop' and return the value in * a pre-allocated buffer. @@ -253,9 +285,6 @@ zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf, uint64_t intval; const char *strval; zprop_source_t src = ZPROP_SRC_NONE; - nvlist_t *nvroot; - vdev_stat_t *vs; - uint_t vsc; if (zpool_get_state(zhp) == POOL_STATE_UNAVAIL) { switch (prop) { @@ -264,7 +293,7 @@ zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf, break; case ZPOOL_PROP_HEALTH: - (void) strlcpy(buf, "FAULTED", len); + (void) strlcpy(buf, zpool_get_state_str(zhp), len); break; case ZPOOL_PROP_GUID: @@ -365,14 +394,7 @@ zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf, break; case ZPOOL_PROP_HEALTH: - verify(nvlist_lookup_nvlist(zpool_get_config(zhp, NULL), - ZPOOL_CONFIG_VDEV_TREE, &nvroot) == 0); - verify(nvlist_lookup_uint64_array(nvroot, - ZPOOL_CONFIG_VDEV_STATS, (uint64_t **)&vs, &vsc) - == 0); - - (void) strlcpy(buf, zpool_state_to_name(intval, - vs->vs_aux), len); + (void) strlcpy(buf, zpool_get_state_str(zhp), len); break; case ZPOOL_PROP_VERSION: if (intval >= SPA_VERSION_FEATURES) { diff --git a/lib/libzfs/libzfs_status.c b/lib/libzfs/libzfs_status.c index 57d2deabf..4089f0cc6 100644 --- a/lib/libzfs/libzfs_status.c +++ b/lib/libzfs/libzfs_status.c @@ -404,12 +404,12 @@ zpool_status_t zpool_get_status(zpool_handle_t *zhp, char **msgid, zpool_errata_t *errata) { zpool_status_t ret = check_status(zhp->zpool_config, B_FALSE, errata); - - if (ret >= NMSGID) - *msgid = NULL; - else - *msgid = zfs_msgid_table[ret]; - + if (msgid != NULL) { + if (ret >= NMSGID) + *msgid = NULL; + else + *msgid = zfs_msgid_table[ret]; + } return (ret); } diff --git a/module/spl/spl-kstat.c b/module/spl/spl-kstat.c index bcbff94a6..c3fc2e4b2 100644 --- a/module/spl/spl-kstat.c +++ b/module/spl/spl-kstat.c @@ -389,7 +389,8 @@ kstat_seq_start(struct seq_file *f, loff_t *pos) ksp->ks_snaptime = gethrtime(); - if (!n && kstat_seq_show_headers(f)) + if (!(ksp->ks_flags & KSTAT_FLAG_NO_HEADERS) && !n && + kstat_seq_show_headers(f)) return (NULL); if (n >= ksp->ks_ndata) @@ -539,7 +540,6 @@ __kstat_create(const char *ks_module, int ks_instance, const char *ks_name, ASSERT(ks_module); ASSERT(ks_instance == 0); ASSERT(ks_name); - ASSERT(!(ks_flags & KSTAT_FLAG_UNSUPPORTED)); if ((ks_type == KSTAT_TYPE_INTR) || (ks_type == KSTAT_TYPE_IO)) ASSERT(ks_ndata == 1); diff --git a/module/zfs/spa_misc.c b/module/zfs/spa_misc.c index 288e5378b..6c7e2f55c 100644 --- a/module/zfs/spa_misc.c +++ b/module/zfs/spa_misc.c @@ -2254,6 +2254,45 @@ spa_set_missing_tvds(spa_t *spa, uint64_t missing) spa->spa_missing_tvds = missing; } +/* + * Return the pool state string ("ONLINE", "DEGRADED", "SUSPENDED", etc). + */ +const char * +spa_state_to_name(spa_t *spa) +{ + vdev_state_t state = spa->spa_root_vdev->vdev_state; + vdev_aux_t aux = spa->spa_root_vdev->vdev_stat.vs_aux; + + if (spa_suspended(spa) && + (spa_get_failmode(spa) != ZIO_FAILURE_MODE_CONTINUE)) + return ("SUSPENDED"); + + switch (state) { + case VDEV_STATE_CLOSED: + case VDEV_STATE_OFFLINE: + return ("OFFLINE"); + case VDEV_STATE_REMOVED: + return ("REMOVED"); + case VDEV_STATE_CANT_OPEN: + if (aux == VDEV_AUX_CORRUPT_DATA || aux == VDEV_AUX_BAD_LOG) + return ("FAULTED"); + else if (aux == VDEV_AUX_SPLIT_POOL) + return ("SPLIT"); + else + return ("UNAVAIL"); + case VDEV_STATE_FAULTED: + return ("FAULTED"); + case VDEV_STATE_DEGRADED: + return ("DEGRADED"); + case VDEV_STATE_HEALTHY: + return ("ONLINE"); + default: + break; + } + + return ("UNKNOWN"); +} + #if defined(_KERNEL) #include @@ -2406,6 +2445,7 @@ EXPORT_SYMBOL(spa_namespace_lock); EXPORT_SYMBOL(spa_trust_config); EXPORT_SYMBOL(spa_missing_tvds_allowed); EXPORT_SYMBOL(spa_set_missing_tvds); +EXPORT_SYMBOL(spa_state_to_name); /* BEGIN CSTYLED */ module_param(zfs_flags, uint, 0644); diff --git a/module/zfs/spa_stats.c b/module/zfs/spa_stats.c index 3f137d9c7..fa1cf9e98 100644 --- a/module/zfs/spa_stats.c +++ b/module/zfs/spa_stats.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include /* * Keeps stats on last N reads per spa_t, disabled by default. @@ -997,6 +999,64 @@ spa_mmp_history_add(spa_t *spa, uint64_t txg, uint64_t timestamp, return ((void *)smh); } +static void * +spa_state_addr(kstat_t *ksp, loff_t n) +{ + return (ksp->ks_private); /* return the spa_t */ +} + +static int +spa_state_data(char *buf, size_t size, void *data) +{ + spa_t *spa = (spa_t *)data; + (void) snprintf(buf, size, "%s\n", spa_state_to_name(spa)); + return (0); +} + +/* + * Return the state of the pool in /proc/spl/kstat/zfs//state. + * + * This is a lock-less read of the pool's state (unlike using 'zpool', which + * can potentially block for seconds). Because it doesn't block, it can useful + * as a pool heartbeat value. + */ +static void +spa_state_init(spa_t *spa) +{ + spa_stats_history_t *ssh = &spa->spa_stats.state; + char *name; + kstat_t *ksp; + + mutex_init(&ssh->lock, NULL, MUTEX_DEFAULT, NULL); + + name = kmem_asprintf("zfs/%s", spa_name(spa)); + ksp = kstat_create(name, 0, "state", "misc", + KSTAT_TYPE_RAW, 0, KSTAT_FLAG_VIRTUAL); + + ssh->kstat = ksp; + if (ksp) { + ksp->ks_lock = &ssh->lock; + ksp->ks_data = NULL; + ksp->ks_private = spa; + ksp->ks_flags |= KSTAT_FLAG_NO_HEADERS; + kstat_set_raw_ops(ksp, NULL, spa_state_data, spa_state_addr); + kstat_install(ksp); + } + + strfree(name); +} + +static void +spa_health_destroy(spa_t *spa) +{ + spa_stats_history_t *ssh = &spa->spa_stats.state; + kstat_t *ksp = ssh->kstat; + if (ksp) + kstat_delete(ksp); + + mutex_destroy(&ssh->lock); +} + void spa_stats_init(spa_t *spa) { @@ -1005,11 +1065,13 @@ spa_stats_init(spa_t *spa) spa_tx_assign_init(spa); spa_io_history_init(spa); spa_mmp_history_init(spa); + spa_state_init(spa); } void spa_stats_destroy(spa_t *spa) { + spa_health_destroy(spa); spa_tx_assign_destroy(spa); spa_txg_history_destroy(spa); spa_read_history_destroy(spa); diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index 0260eb884..b56bc2ebd 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -575,6 +575,10 @@ tests = ['inuse_001_pos', 'inuse_003_pos', 'inuse_004_pos', post = tags = ['functional', 'inuse'] +[tests/functional/kstat] +tests = ['state'] +tags = ['functional', 'kstat'] + [tests/functional/large_files] tests = ['large_files_001_pos', 'large_files_002_pos'] tags = ['functional', 'large_files'] diff --git a/tests/zfs-tests/include/blkdev.shlib b/tests/zfs-tests/include/blkdev.shlib index 87ffa8560..5163ea2ae 100644 --- a/tests/zfs-tests/include/blkdev.shlib +++ b/tests/zfs-tests/include/blkdev.shlib @@ -421,7 +421,16 @@ function unload_scsi_debug # function get_debug_device { - lsscsi | nawk '/scsi_debug/ {print $6; exit}' | cut -d / -f3 + for i in {1..10} ; do + val=$(lsscsi | nawk '/scsi_debug/ {print $6; exit}' | cut -d / -f3) + + # lsscsi can take time to settle + if [ "$val" != "-" ] ; then + break + fi + sleep 1 + done + echo "$val" } # diff --git a/tests/zfs-tests/tests/functional/Makefile.am b/tests/zfs-tests/tests/functional/Makefile.am index 396124986..2368b829b 100644 --- a/tests/zfs-tests/tests/functional/Makefile.am +++ b/tests/zfs-tests/tests/functional/Makefile.am @@ -28,6 +28,7 @@ SUBDIRS = \ hkdf \ inheritance \ inuse \ + kstat \ large_files \ largest_pool \ libzfs \ diff --git a/tests/zfs-tests/tests/functional/kstat/Makefile.am b/tests/zfs-tests/tests/functional/kstat/Makefile.am new file mode 100644 index 000000000..8ad83ec3e --- /dev/null +++ b/tests/zfs-tests/tests/functional/kstat/Makefile.am @@ -0,0 +1,5 @@ +pkgdatadir = $(datadir)/@PACKAGE@/zfs-tests/tests/functional/kstat +dist_pkgdata_SCRIPTS = \ + setup.ksh \ + cleanup.ksh \ + state.ksh diff --git a/tests/zfs-tests/tests/functional/kstat/cleanup.ksh b/tests/zfs-tests/tests/functional/kstat/cleanup.ksh new file mode 100755 index 000000000..8a212ce37 --- /dev/null +++ b/tests/zfs-tests/tests/functional/kstat/cleanup.ksh @@ -0,0 +1,28 @@ +#!/bin/ksh -p +# +# 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 http://www.opensolaris.org/os/licensing. +# 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) 2018 by Lawrence Livermore National Security, LLC. +# + +. $STF_SUITE/include/libtest.shlib + +default_cleanup diff --git a/tests/zfs-tests/tests/functional/kstat/setup.ksh b/tests/zfs-tests/tests/functional/kstat/setup.ksh new file mode 100755 index 000000000..57717a096 --- /dev/null +++ b/tests/zfs-tests/tests/functional/kstat/setup.ksh @@ -0,0 +1,34 @@ +#!/bin/ksh -p +# +# 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 http://www.opensolaris.org/os/licensing. +# 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) 2018 by Lawrence Livermore National Security, LLC. +# + +. $STF_SUITE/include/libtest.shlib + +if ! is_linux ; then + log_unsupported "/proc/spl/kstat//health only supported on Linux" +fi + +default_mirror_setup $DISKS + +log_pass diff --git a/tests/zfs-tests/tests/functional/kstat/state.ksh b/tests/zfs-tests/tests/functional/kstat/state.ksh new file mode 100755 index 000000000..bf0b6e313 --- /dev/null +++ b/tests/zfs-tests/tests/functional/kstat/state.ksh @@ -0,0 +1,144 @@ +#!/bin/ksh -p +# +# 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 http://www.opensolaris.org/os/licensing. +# 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) 2018 by Lawrence Livermore National Security, LLC. +# + +# +# DESCRIPTION: +# Test /proc/spl/kstat/zfs//state kstat +# +# STRATEGY: +# 1. Create a mirrored pool +# 2. Check that pool is ONLINE +# 3. Fault one disk +# 4. Check that pool is DEGRADED +# 5. Create a new pool with a single scsi_debug disk +# 6. Remove the disk +# 7. Check that pool is SUSPENDED +# 8. Add the disk back in +# 9. Clear errors and destroy the pools + +. $STF_SUITE/include/libtest.shlib + +verify_runnable "both" + +function cleanup +{ + # Destroy the scsi_debug pool + if [ -n "$TESTPOOL2" ] ; then + if [ -n "$host" ] ; then + # Re-enable the disk + scan_scsi_hosts $host + + # Device may have changed names after being inserted + SDISK=$(get_debug_device) + log_must ln $DEV_RDSKDIR/$SDISK $REALDISK + fi + + # Restore our working pool image + if [ -n "$BACKUP" ] ; then + gunzip -c $BACKUP > $REALDISK + log_must rm -f $BACKUP + fi + + # Our disk is back. Now we can clear errors and destroy the + # pool cleanly. + log_must zpool clear $TESTPOOL2 + + # Now that the disk is back and errors cleared, wait for our + # hung 'zpool scrub' to finish. + wait + + destroy_pool $TESTPOOL2 + log_must rm $REALDISK + unload_scsi_debug + fi +} + +# Check that our pool state values match what's expected +# +# $1: pool name +# $2: expected state ("ONLINE", "DEGRADED", "SUSPENDED", etc) +function check_all +{ + pool=$1 + expected=$2 + + state1=$(zpool status $pool | awk '/state: /{print $2}'); + state2=$(zpool list -H -o health $pool) + state3=$(cat /proc/spl/kstat/zfs/$pool/state) + log_note "Checking $expected = $state1 = $state2 = $state3" + if [[ "$expected" == "$state1" && "$expected" == "$state2" && \ + "$expected" == "$state3" ]] ; then + true + else + false + fi +} + +log_onexit cleanup + +log_assert "Testing /proc/spl/kstat/zfs//state kstat" + +# Test that the initial pool is healthy +check_all $TESTPOOL "ONLINE" + +# Fault one of the disks, and check that pool is degraded +DISK1=$(echo "$DISKS" | awk '{print $2}') +zpool offline -tf $TESTPOOL $DISK1 +check_all $TESTPOOL "DEGRADED" + +# Create a new pool out of a scsi_debug disk +TESTPOOL2=testpool2 +MINVDEVSIZE_MB=$((MINVDEVSIZE / 1048576)) +load_scsi_debug $MINVDEVSIZE_MB 1 1 1 '512b' + +SDISK=$(get_debug_device) +host=$(get_scsi_host $SDISK) + +# Use $REALDISK instead of $SDISK in our pool because $SDISK can change names +# as we remove/add the disk (i.e. /dev/sdf -> /dev/sdg). +REALDISK=/dev/kstat-state-realdisk +log_must [ ! -e $REALDISK ] +ln $DEV_RDSKDIR/$SDISK $REALDISK + +log_must zpool create $TESTPOOL2 $REALDISK + +# Backup the contents of the disk image +BACKUP=/tmp/kstat-state-realdisk.gz +log_must [ ! -e $BACKUP ] +gzip -c $REALDISK > $BACKUP + +# Yank out the disk from under the pool +log_must rm $REALDISK +remove_disk $SDISK + +# Run a 'zpool scrub' in the background to suspend the pool. We run it in the +# background since the command will hang when the pool gets suspended. The +# command will resume and exit after we restore the missing disk later on. +zpool scrub $TESTPOOL2 & +sleep 1 # Give the scrub some time to run before we check if it fails + +log_must check_all $TESTPOOL2 "SUSPENDED" + +log_pass "/proc/spl/kstat/zfs//state test successful"