diff --git a/include/Makefile.am b/include/Makefile.am index f173064efc..124dba4116 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -176,6 +176,7 @@ COMMON_H = \ KERNEL_H = \ + sys/spa_json_stats.h \ sys/zfs_ioctl.h \ sys/zfs_ioctl_impl.h \ sys/zfs_onexit.h \ diff --git a/include/os/freebsd/spl/sys/kstat.h b/include/os/freebsd/spl/sys/kstat.h index 7dc2c4753b..b93b4540af 100644 --- a/include/os/freebsd/spl/sys/kstat.h +++ b/include/os/freebsd/spl/sys/kstat.h @@ -67,8 +67,8 @@ struct list_head {}; #define KSTAT_FLAG_VIRTUAL 0x01 #define KSTAT_FLAG_VAR_SIZE 0x02 #define KSTAT_FLAG_WRITABLE 0x04 -#define KSTAT_FLAG_PERSISTENT 0x08 -#define KSTAT_FLAG_DORMANT 0x10 +#define KSTAT_FLAG_RESTRICTED 0x08 +#define KSTAT_FLAG_UNUSED 0x10 #define KSTAT_FLAG_INVALID 0x20 #define KSTAT_FLAG_LONGSTRINGS 0x40 #define KSTAT_FLAG_NO_HEADERS 0x80 diff --git a/include/os/linux/spl/sys/kstat.h b/include/os/linux/spl/sys/kstat.h index 305c411ddf..ce08d387ad 100644 --- a/include/os/linux/spl/sys/kstat.h +++ b/include/os/linux/spl/sys/kstat.h @@ -66,8 +66,8 @@ #define KSTAT_FLAG_VIRTUAL 0x01 #define KSTAT_FLAG_VAR_SIZE 0x02 #define KSTAT_FLAG_WRITABLE 0x04 -#define KSTAT_FLAG_PERSISTENT 0x08 -#define KSTAT_FLAG_DORMANT 0x10 +#define KSTAT_FLAG_RESTRICTED 0x08 +#define KSTAT_FLAG_UNUSED 0x10 #define KSTAT_FLAG_INVALID 0x20 #define KSTAT_FLAG_LONGSTRINGS 0x40 #define KSTAT_FLAG_NO_HEADERS 0x80 diff --git a/include/sys/nvpair.h b/include/sys/nvpair.h index 2dbd9e3eaf..e22c5b5979 100644 --- a/include/sys/nvpair.h +++ b/include/sys/nvpair.h @@ -213,6 +213,8 @@ _SYS_NVPAIR_H int nvlist_remove(nvlist_t *, const char *, data_type_t); _SYS_NVPAIR_H int nvlist_remove_all(nvlist_t *, const char *); _SYS_NVPAIR_H int nvlist_remove_nvpair(nvlist_t *, nvpair_t *); +_SYS_NVPAIR_H int nvlist_to_json(nvlist_t *, char **, size_t); + _SYS_NVPAIR_H int nvlist_lookup_boolean(const nvlist_t *, const char *); _SYS_NVPAIR_H int nvlist_lookup_boolean_value(const nvlist_t *, const char *, boolean_t *); diff --git a/include/sys/spa.h b/include/sys/spa.h index aa66d489ef..5575617766 100644 --- a/include/sys/spa.h +++ b/include/sys/spa.h @@ -771,6 +771,9 @@ extern void spa_scan_stat_init(spa_t *spa); extern int spa_scan_get_stats(spa_t *spa, pool_scan_stat_t *ps); extern int bpobj_enqueue_alloc_cb(void *arg, const blkptr_t *bp, dmu_tx_t *tx); extern int bpobj_enqueue_free_cb(void *arg, const blkptr_t *bp, dmu_tx_t *tx); +extern void spa_add_spares(spa_t *spa, nvlist_t *config); +extern void spa_add_l2cache(spa_t *spa, nvlist_t *config); +extern void spa_add_feature_stats(spa_t *spa, nvlist_t *config); #define SPA_ASYNC_CONFIG_UPDATE 0x01 #define SPA_ASYNC_REMOVE 0x02 diff --git a/include/sys/spa_impl.h b/include/sys/spa_impl.h index 7811abbb9c..9a3c3b8146 100644 --- a/include/sys/spa_impl.h +++ b/include/sys/spa_impl.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -442,6 +443,7 @@ struct spa { uint64_t spa_autotrim; /* automatic background trim? */ uint64_t spa_errata; /* errata issues detected */ spa_stats_t spa_stats; /* assorted spa statistics */ + spa_json_stats_t spa_json_stats; /* diagnostic status in JSON */ spa_keystore_t spa_keystore; /* loaded crypto keys */ /* arc_memory_throttle() parameters during low memory condition */ diff --git a/include/sys/spa_json_stats.h b/include/sys/spa_json_stats.h new file mode 100644 index 0000000000..00b4776611 --- /dev/null +++ b/include/sys/spa_json_stats.h @@ -0,0 +1,40 @@ +/* + * 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://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) 2024, Klara Inc. + */ + +#ifndef _SYS_SPA_JSON_STATS_H +#define _SYS_SPA_JSON_STATS_H + +#include +#include +#include + +typedef struct spa_json_stats { + kmutex_t lock; + kstat_t *kstat; +} spa_json_stats_t; + +extern int spa_generate_json_stats(spa_t *spa, char *buf, size_t size); + +#endif /* _SYS_SPA_JSON_STATS_H */ diff --git a/lib/libnvpair/libnvpair.abi b/lib/libnvpair/libnvpair.abi index 69009375e8..4126c379d2 100644 --- a/lib/libnvpair/libnvpair.abi +++ b/lib/libnvpair/libnvpair.abi @@ -117,6 +117,7 @@ + @@ -2780,6 +2781,12 @@ + + + + + + diff --git a/lib/libspl/include/sys/kstat.h b/lib/libspl/include/sys/kstat.h index e6057bf6b9..5721cd359b 100644 --- a/lib/libspl/include/sys/kstat.h +++ b/lib/libspl/include/sys/kstat.h @@ -239,24 +239,13 @@ typedef struct kstat { * The ks_snapshot routine (see below) does not need to check for * this; permission checking is handled in the kstat driver. * - * KSTAT_FLAG_PERSISTENT: + * KSTAT_FLAG_RESTRICTED: * - * Indicates that this kstat is to be persistent over time. - * For persistent kstats, kstat_delete() simply marks the - * kstat as dormant; a subsequent kstat_create() reactivates - * the kstat. This feature is provided so that statistics - * are not lost across driver close/open (e.g., raw disk I/O - * on a disk with no mounted partitions.) - * NOTE: Persistent kstats cannot be virtual, since ks_data - * points to garbage as soon as the driver goes away. + * Indicates that this kstat has restricted access and is + * not world readable. * * The following flags are maintained by the kstat framework: * - * KSTAT_FLAG_DORMANT: - * - * For persistent kstats, indicates that the kstat is in the - * dormant state (e.g., the corresponding device is closed). - * * KSTAT_FLAG_INVALID: * * This flag is set when a kstat is in a transitional state, @@ -268,8 +257,8 @@ typedef struct kstat { #define KSTAT_FLAG_VIRTUAL 0x01 #define KSTAT_FLAG_VAR_SIZE 0x02 #define KSTAT_FLAG_WRITABLE 0x04 -#define KSTAT_FLAG_PERSISTENT 0x08 -#define KSTAT_FLAG_DORMANT 0x10 +#define KSTAT_FLAG_RESTRICTED 0x08 +#define KSTAT_FLAG_UNUSED 0x10 #define KSTAT_FLAG_INVALID 0x20 #define KSTAT_FLAG_LONGSTRINGS 0x40 #define KSTAT_FLAG_NO_HEADERS 0x80 @@ -722,33 +711,8 @@ extern void kstat_init(void); /* initialize kstat framework */ * you must NOT be holding that kstat's ks_lock. Otherwise, you may * deadlock with a kstat reader. * - * Persistent kstats - * - * From the provider's point of view, persistence is transparent. The only - * difference between ephemeral (normal) kstats and persistent kstats - * is that you pass KSTAT_FLAG_PERSISTENT to kstat_create(). Magically, - * this has the effect of making your data visible even when you're - * not home. Persistence is important to tools like iostat, which want - * to get a meaningful picture of disk activity. Without persistence, - * raw disk i/o statistics could never accumulate: they would come and - * go with each open/close of the raw device. - * - * The magic of persistence works by slightly altering the behavior of - * kstat_create() and kstat_delete(). The first call to kstat_create() - * creates a new kstat, as usual. However, kstat_delete() does not - * actually delete the kstat: it performs one final update of the data - * (i.e., calls the ks_update routine), marks the kstat as dormant, and - * sets the ks_lock, ks_update, ks_private, and ks_snapshot fields back - * to their default values (since they might otherwise point to garbage, - * e.g. if the provider is going away). kstat clients can still access - * the dormant kstat just like a live kstat; they just continue to see - * the final data values as long as the kstat remains dormant. - * All subsequent kstat_create() calls simply find the already-existing, - * dormant kstat and return a pointer to it, without altering any fields. - * The provider then performs its usual initialization sequence, and - * calls kstat_install(). kstat_install() uses the old data values to - * initialize the native data (i.e., ks_update is called with KSTAT_WRITE), - * thus making it seem like you were never gone. + * Persistent kstats are not implemented since there is no persistent + * namespace for them to reside. */ extern kstat_t *kstat_create(const char *, int, const char *, const char *, diff --git a/lib/libzpool/Makefile.am b/lib/libzpool/Makefile.am index ff30af7d2b..910dd2d9d3 100644 --- a/lib/libzpool/Makefile.am +++ b/lib/libzpool/Makefile.am @@ -132,6 +132,7 @@ nodist_libzpool_la_SOURCES = \ module/zfs/spa_config.c \ module/zfs/spa_errlog.c \ module/zfs/spa_history.c \ + module/zfs/spa_json_stats.c \ module/zfs/spa_log_spacemap.c \ module/zfs/spa_misc.c \ module/zfs/spa_stats.c \ diff --git a/module/Kbuild.in b/module/Kbuild.in index 0472a9348c..55b4b45bcc 100644 --- a/module/Kbuild.in +++ b/module/Kbuild.in @@ -373,6 +373,7 @@ ZFS_OBJS := \ spa_config.o \ spa_errlog.o \ spa_history.o \ + spa_json_stats.o \ spa_log_spacemap.o \ spa_misc.o \ spa_stats.o \ diff --git a/module/Makefile.bsd b/module/Makefile.bsd index 9161204c99..dbf2da59cc 100644 --- a/module/Makefile.bsd +++ b/module/Makefile.bsd @@ -304,6 +304,7 @@ SRCS+= abd.c \ spa_config.c \ spa_errlog.c \ spa_history.c \ + spa_json_stats.c \ spa_log_spacemap.c \ spa_misc.c \ spa_stats.c \ diff --git a/module/nvpair/nvpair.c b/module/nvpair/nvpair.c index 887f7d32df..af64d5aa92 100644 --- a/module/nvpair/nvpair.c +++ b/module/nvpair/nvpair.c @@ -994,6 +994,369 @@ nvlist_remove_nvpair(nvlist_t *nvl, nvpair_t *nvp) return (0); } +#define JPRINTF(start, end, ...) \ +do { \ + if (start < end) { \ + int ret = snprintf(start, end - start, __VA_ARGS__); \ + if (ret < 0) \ + return (ENOMEM); \ + start += ret; \ + } else \ + return (ENOMEM); \ +} while (0) + +static int +nvlist_json_string(const char *s, char **buf, size_t size) +{ + char *p = *buf; + char *end = *buf + size; + static const char *hex = "0123456789ABCDEF"; + int c; + + if (s == NULL) { + JPRINTF(p, end, "null"); + *buf = p; + return (0); + } + + JPRINTF(p, end, "\""); + while (*s) { + c = (int)*s++; + /* formfeed, newline, return, tab, backspace */ + if (c == 12) + JPRINTF(p, end, "\\f"); + else if (c == 10) + JPRINTF(p, end, "\\n"); + else if (c == 13) + JPRINTF(p, end, "\\r"); + else if (c == 9) + JPRINTF(p, end, "\\t"); + else if (c == 8) + JPRINTF(p, end, "\\b"); + /* + * all characters from 0x00 to 0x1f, and 0x7f are + * escaped as: \u00xx + */ + else if (((0 <= c) && (c <= 0x1f)) || (c == 0x7f)) { + JPRINTF(p, end, "\\u00%c%c", + hex[(c >> 4) & 0x0f], hex[c & 0x0f]); + } else if (c == '"') + JPRINTF(p, end, "\\\""); + else if (c == '\\') + JPRINTF(p, end, "\\\\"); + else if (c == '/') + JPRINTF(p, end, "\\/"); + /* + * all other printable characters ' ' to '~', and + * any utf-8 sequences (high bit set): + * 1xxxxxxx 10xxxxxx ... + * is a utf-8 sequence (10xxxxxx may occur 1 to 3 times). + * Note that this is simply distinguished here as high + * bit set. + */ + else + JPRINTF(p, end, "%c", c); + } + JPRINTF(p, end, "\""); + *buf = p; + return (0); +} + +int +nvlist_to_json(nvlist_t *nvl, char **buf, size_t size) +{ + boolean_t first = B_TRUE; + char *p = *buf; + char *end = *buf + size; + nvpair_t *curr = nvlist_next_nvpair(nvl, NULL); + + JPRINTF(p, end, "{"); + + while (curr) { + if (!first) + JPRINTF(p, end, ","); + else + first = B_FALSE; + + if (nvlist_json_string(nvpair_name(curr), &p, end - p) != 0) + return (ENOMEM); + JPRINTF(p, end, ":"); + + switch (nvpair_type(curr)) { + case DATA_TYPE_STRING: { + if (nvlist_json_string(fnvpair_value_string(curr), &p, + end - p) != 0) + return (ENOMEM); + break; + } + + case DATA_TYPE_BOOLEAN: { + JPRINTF(p, end, "true"); + break; + } + + case DATA_TYPE_BOOLEAN_VALUE: { + JPRINTF(p, end, "%s", + fnvpair_value_boolean_value(curr) == B_TRUE ? + "true" : "false"); + break; + } + + case DATA_TYPE_BYTE: { + JPRINTF(p, end, "%hhu", fnvpair_value_byte(curr)); + break; + } + + case DATA_TYPE_INT8: { + JPRINTF(p, end, "%hhd", fnvpair_value_int8(curr)); + break; + } + + case DATA_TYPE_UINT8: { + JPRINTF(p, end, "%hhu", fnvpair_value_uint8(curr)); + break; + } + + case DATA_TYPE_INT16: { + JPRINTF(p, end, "%hd", fnvpair_value_int16(curr)); + break; + } + + case DATA_TYPE_UINT16: { + JPRINTF(p, end, "%hu", fnvpair_value_uint16(curr)); + break; + } + + case DATA_TYPE_INT32: { + JPRINTF(p, end, "%d", fnvpair_value_int32(curr)); + break; + } + + case DATA_TYPE_UINT32: { + JPRINTF(p, end, "%u", fnvpair_value_uint32(curr)); + break; + } + + case DATA_TYPE_INT64: { + JPRINTF(p, end, "%lld", + (long long)fnvpair_value_int64(curr)); + break; + } + + case DATA_TYPE_UINT64: { + JPRINTF(p, end, "%llu", + (unsigned long long)fnvpair_value_uint64(curr)); + break; + } + + case DATA_TYPE_HRTIME: { + hrtime_t val; + VERIFY0(nvpair_value_hrtime(curr, &val)); + JPRINTF(p, end, "%llu", (unsigned long long)val); + break; + } + +#if !defined(_KERNEL) + case DATA_TYPE_DOUBLE: { + double val; + VERIFY0(nvpair_value_double(curr, &val)); + JPRINTF(p, end, "%f", val); + break; + } +#endif + + case DATA_TYPE_NVLIST: { + if (nvlist_to_json(fnvpair_value_nvlist(curr), &p, + end - p) != 0) + return (ENOMEM); + break; + } + + case DATA_TYPE_STRING_ARRAY: { + const char **val; + uint_t valsz, i; + VERIFY0(nvpair_value_string_array(curr, &val, &valsz)); + JPRINTF(p, end, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + JPRINTF(p, end, ","); + if (nvlist_json_string(val[i], &p, + end - p) != 0) + return (ENOMEM); + } + JPRINTF(p, end, "]"); + break; + } + + case DATA_TYPE_NVLIST_ARRAY: { + nvlist_t **val; + uint_t valsz, i; + VERIFY0(nvpair_value_nvlist_array(curr, &val, &valsz)); + JPRINTF(p, end, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + JPRINTF(p, end, ","); + if (nvlist_to_json(val[i], &p, end - p) != 0) + return (ENOMEM); + } + JPRINTF(p, end, "]"); + break; + } + + case DATA_TYPE_BOOLEAN_ARRAY: { + boolean_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_boolean_array(curr, &val, &valsz)); + JPRINTF(p, end, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + JPRINTF(p, end, ","); + JPRINTF(p, end, val[i] == B_TRUE ? + "true" : "false"); + } + JPRINTF(p, end, "]"); + break; + } + + case DATA_TYPE_BYTE_ARRAY: { + uchar_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_byte_array(curr, &val, &valsz)); + JPRINTF(p, end, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + JPRINTF(p, end, ","); + JPRINTF(p, end, "%hhu", val[i]); + } + JPRINTF(p, end, "]"); + break; + } + + case DATA_TYPE_UINT8_ARRAY: { + uint8_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_uint8_array(curr, &val, &valsz)); + JPRINTF(p, end, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + JPRINTF(p, end, ","); + JPRINTF(p, end, "%hhu", val[i]); + } + JPRINTF(p, end, "]"); + break; + } + + case DATA_TYPE_INT8_ARRAY: { + int8_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_int8_array(curr, &val, &valsz)); + JPRINTF(p, end, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + JPRINTF(p, end, ","); + JPRINTF(p, end, "%hhd", val[i]); + } + JPRINTF(p, end, "]"); + break; + } + + case DATA_TYPE_UINT16_ARRAY: { + uint16_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_uint16_array(curr, &val, &valsz)); + JPRINTF(p, end, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + JPRINTF(p, end, ","); + JPRINTF(p, end, "%hu", val[i]); + } + JPRINTF(p, end, "]"); + break; + } + + case DATA_TYPE_INT16_ARRAY: { + int16_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_int16_array(curr, &val, &valsz)); + JPRINTF(p, end, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) { + JPRINTF(p, end, ","); + } + JPRINTF(p, end, "%hd", val[i]); + } + JPRINTF(p, end, "]"); + break; + } + + case DATA_TYPE_UINT32_ARRAY: { + uint32_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_uint32_array(curr, &val, &valsz)); + JPRINTF(p, end, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + JPRINTF(p, end, ","); + JPRINTF(p, end, "%u", val[i]); + } + JPRINTF(p, end, "]"); + break; + } + + case DATA_TYPE_INT32_ARRAY: { + int32_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_int32_array(curr, &val, &valsz)); + JPRINTF(p, end, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + JPRINTF(p, end, ","); + JPRINTF(p, end, "%d", val[i]); + } + JPRINTF(p, end, "]"); + break; + } + + case DATA_TYPE_UINT64_ARRAY: { + uint64_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_uint64_array(curr, &val, &valsz)); + JPRINTF(p, end, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + JPRINTF(p, end, ","); + JPRINTF(p, end, "%llu", + (unsigned long long)val[i]); + } + JPRINTF(p, end, "]"); + break; + } + + case DATA_TYPE_INT64_ARRAY: { + int64_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_int64_array(curr, &val, &valsz)); + JPRINTF(p, end, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + JPRINTF(p, end, ","); + JPRINTF(p, end, "%lld", (long long)val[i]); + } + JPRINTF(p, end, "]"); + break; + } + + case DATA_TYPE_UNKNOWN: + case DATA_TYPE_DONTCARE: + return (-1); + } + curr = nvlist_next_nvpair(nvl, curr); + } + JPRINTF(p, end, "}"); + *buf = p; + return (0); +} + /* * This function calculates the size of an nvpair value. * diff --git a/module/os/linux/spl/spl-kstat.c b/module/os/linux/spl/spl-kstat.c index ad553a73a6..d087d905d1 100644 --- a/module/os/linux/spl/spl-kstat.c +++ b/module/os/linux/spl/spl-kstat.c @@ -654,7 +654,7 @@ __kstat_install(kstat_t *ksp) ASSERT(ksp); mode_t mode; /* Specify permission modes for different kstats */ - if (strncmp(ksp->ks_proc.kpe_name, "dbufs", KSTAT_STRLEN) == 0) { + if (ksp->ks_flags & KSTAT_FLAG_RESTRICTED) { mode = 0600; } else { mode = 0644; diff --git a/module/zfs/dbuf_stats.c b/module/zfs/dbuf_stats.c index ccee8997e1..3a4b49fd46 100644 --- a/module/zfs/dbuf_stats.c +++ b/module/zfs/dbuf_stats.c @@ -189,7 +189,7 @@ dbuf_stats_hash_table_init(dbuf_hash_table_t *hash) dsh->hash = hash; ksp = kstat_create("zfs", 0, "dbufs", "misc", - KSTAT_TYPE_RAW, 0, KSTAT_FLAG_VIRTUAL); + KSTAT_TYPE_RAW, 0, KSTAT_FLAG_VIRTUAL | KSTAT_FLAG_RESTRICTED); dsh->kstat = ksp; if (ksp) { diff --git a/module/zfs/spa.c b/module/zfs/spa.c index 1a68a09535..0f30e52398 100644 --- a/module/zfs/spa.c +++ b/module/zfs/spa.c @@ -5942,7 +5942,7 @@ spa_inject_delref(spa_t *spa) /* * Add spares device information to the nvlist. */ -static void +void spa_add_spares(spa_t *spa, nvlist_t *config) { nvlist_t **spares; @@ -5992,7 +5992,7 @@ spa_add_spares(spa_t *spa, nvlist_t *config) /* * Add l2cache device information to the nvlist, including vdev stats. */ -static void +void spa_add_l2cache(spa_t *spa, nvlist_t *config) { nvlist_t **l2cache; @@ -6104,7 +6104,7 @@ spa_feature_stats_from_cache(spa_t *spa, nvlist_t *features) * ensures we don't block here on I/O on a suspended pool so 'zpool * clear' can resume the pool. */ -static void +void spa_add_feature_stats(spa_t *spa, nvlist_t *config) { nvlist_t *features; diff --git a/module/zfs/spa_json_stats.c b/module/zfs/spa_json_stats.c new file mode 100644 index 0000000000..51700484ac --- /dev/null +++ b/module/zfs/spa_json_stats.c @@ -0,0 +1,466 @@ +/* + * 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://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) 2024, Klara Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define JSON_STATUS_VERSION 4 + +static const char * +vdev_state_string(vdev_state_t state, vdev_aux_t aux) +{ + const char *s; + switch (state) { + case VDEV_STATE_UNKNOWN: s = "HEALTHY"; break; + case VDEV_STATE_CLOSED: s = "CLOSED"; break; + case VDEV_STATE_OFFLINE: s = "OFFLINE"; break; + case VDEV_STATE_REMOVED: s = "REMOVED"; break; + case VDEV_STATE_CANT_OPEN: + if (aux == VDEV_AUX_CORRUPT_DATA || aux == VDEV_AUX_BAD_LOG) + s = "FAULTED"; + else if (aux == VDEV_AUX_SPLIT_POOL) + s = "SPLIT"; + else + s = "UNAVAIL"; + break; + case VDEV_STATE_FAULTED: s = "FAULTED"; break; + case VDEV_STATE_DEGRADED: s = "DEGRADED"; break; + case VDEV_STATE_HEALTHY: s = "HEALTHY"; break; + default: s = "?"; + } + return (s); +} + +static void +vdev_to_nvlist(vdev_t *vd, pool_scan_stat_t *ps, nvlist_t *tree) +{ + uint64_t n; + int nparity = vdev_get_nparity(vd); + vdev_t **a; + const char *s; + nvlist_t *init_state, *trim_state; + + nvlist_add_string(tree, "type", vd->vdev_ops->vdev_op_type); + + /* dRAID vdevs have additional config keys */ + if (vd->vdev_ops == &vdev_draid_ops && + vd->vdev_ops->vdev_op_config_generate != NULL) { + nvlist_t *nvl = fnvlist_alloc(); + vd->vdev_ops->vdev_op_config_generate(vd, nvl); + fnvlist_merge(tree, nvl); + nvlist_free(nvl); + } else if (nparity > 0) { + /* RAIDZ parity */ + fnvlist_add_uint64(tree, "nparity", nparity); + } + + fnvlist_add_uint64(tree, "id", vd->vdev_id); + fnvlist_add_uint64(tree, "guid", vd->vdev_guid); + if (strcmp(vd->vdev_ops->vdev_op_type, "root") != 0) { + fnvlist_add_uint64(tree, "asize", vd->vdev_asize); + fnvlist_add_uint64(tree, "ashift", vd->vdev_ashift); + if (vd->vdev_ops->vdev_op_leaf) { + fnvlist_add_boolean_value(tree, "whole_disk", + (vd->vdev_wholedisk == 0) ? B_FALSE : B_TRUE); + } + fnvlist_add_boolean_value(tree, "offline", + (vd->vdev_offline == 0) ? B_FALSE : B_TRUE); + fnvlist_add_boolean_value(tree, "faulted", + (vd->vdev_faulted == 0) ? B_FALSE : B_TRUE); + fnvlist_add_boolean_value(tree, "degraded", + (vd->vdev_degraded == 0) ? B_FALSE : B_TRUE); + fnvlist_add_boolean_value(tree, "removed", + (vd->vdev_removed == 0) ? B_FALSE : B_TRUE); + fnvlist_add_boolean_value(tree, "not_present", + (vd->vdev_not_present == 0) ? B_FALSE : B_TRUE); + fnvlist_add_boolean_value(tree, "is_log", + (vd->vdev_islog == 0) ? B_FALSE : B_TRUE); + + if (vd->vdev_path != NULL) + fnvlist_add_string(tree, "path", vd->vdev_path); + if (vd->vdev_devid != NULL) + fnvlist_add_string(tree, "devid", vd->vdev_devid); + if (vd->vdev_physpath != NULL) + fnvlist_add_string(tree, "physpath", vd->vdev_physpath); + if (vd->vdev_enc_sysfs_path != NULL) { + fnvlist_add_string(tree, "enc_sysfs_path", + vd->vdev_enc_sysfs_path); + } + fnvlist_add_string(tree, "state", + vdev_state_string(vd->vdev_state, vd->vdev_stat.vs_aux)); + /* + * Try for some of the extended status annotations that + * zpool status provides. + */ + fnvlist_add_boolean_value(tree, "vs_scan_removing", + vd->vdev_stat.vs_scan_removing != 0); + fnvlist_add_boolean_value(tree, "vs_noalloc", + vd->vdev_stat.vs_noalloc != 0); + fnvlist_add_boolean_value(tree, "vs_resilver_deferred", + vd->vdev_stat.vs_resilver_deferred); + s = "none"; + if ((vd->vdev_state == VDEV_STATE_UNKNOWN) || + (vd->vdev_state == VDEV_STATE_HEALTHY)) { + if (vd->vdev_stat.vs_scan_processed != 0) { + if (ps && + (ps->pss_state == DSS_SCANNING)) { + s = (ps->pss_func == + POOL_SCAN_RESILVER) ? + "resilvering" : "repairing"; + } else if (ps && + vd->vdev_stat.vs_resilver_deferred) { + s = "awaiting resilver"; + } + } + } + fnvlist_add_string(tree, "resilver_repair", s); + + init_state = fnvlist_alloc(); + s = "VDEV_INITIALIZE_NONE"; + if (vd->vdev_stat.vs_initialize_state == VDEV_INITIALIZE_ACTIVE) + s = "VDEV_INITIALIZE_ACTIVE"; + if (vd->vdev_stat.vs_initialize_state == + VDEV_INITIALIZE_SUSPENDED) + s = "VDEV_INITIALIZE_SUSPENDED"; + if (vd->vdev_stat.vs_initialize_state == + VDEV_INITIALIZE_COMPLETE) + s = "VDEV_INITIALIZE_COMPLETE"; + fnvlist_add_string(init_state, "vs_initialize_state", s); + fnvlist_add_uint64(init_state, "vs_initialize_bytes_done:", + vd->vdev_stat.vs_initialize_bytes_done); + fnvlist_add_uint64(init_state, "vs_initialize_bytes_est", + vd->vdev_stat.vs_initialize_bytes_est); + fnvlist_add_uint64(init_state, "vs_initialize_action_time", + vd->vdev_stat.vs_initialize_action_time); + fnvlist_add_nvlist(tree, "initialize_state", init_state); + fnvlist_free(init_state); + + trim_state = fnvlist_alloc(); + s = "VDEV_UNTRIMMED"; + if (vd->vdev_stat.vs_trim_state == VDEV_TRIM_ACTIVE) + s = "VDEV_TRIM_ACTIVE"; + if (vd->vdev_stat.vs_trim_state == VDEV_TRIM_SUSPENDED) + s = "VDEV_TRIM_SUSPENDED"; + if (vd->vdev_stat.vs_trim_state == VDEV_TRIM_COMPLETE) + s = "VDEV_TRIM_COMPLETE"; + if (vd->vdev_stat.vs_trim_notsup) + s = "VDEV_TRIM_UNSUPPORTED"; + fnvlist_add_string(trim_state, "vs_trim_state", s); + if (!vd->vdev_stat.vs_trim_notsup) { + fnvlist_add_uint64(trim_state, "vs_trim_action_time", + vd->vdev_stat.vs_trim_action_time); + fnvlist_add_uint64(trim_state, "vs_trim_bytes_done", + vd->vdev_stat.vs_trim_bytes_done); + fnvlist_add_uint64(trim_state, "vs_trim_bytes_est", + vd->vdev_stat.vs_trim_bytes_est); + } + fnvlist_add_nvlist(tree, "trim_state", trim_state); + fnvlist_free(trim_state); + + fnvlist_add_uint64(tree, "read_errors", + vd->vdev_stat.vs_read_errors); + fnvlist_add_uint64(tree, "write_errors", + vd->vdev_stat.vs_write_errors); + fnvlist_add_uint64(tree, "checksum_errors", + vd->vdev_stat.vs_checksum_errors); + fnvlist_add_uint64(tree, "slow_ios", + vd->vdev_stat.vs_slow_ios); + fnvlist_add_uint64(tree, "trim_errors", + vd->vdev_stat.vs_trim_errors); + } + n = vd->vdev_children; + a = vd->vdev_child; + if (n != 0) { + fnvlist_add_uint64(tree, "vdev_children", n); + nvlist_t **ch = kmem_alloc(sizeof (nvlist_t *) * n, KM_NOSLEEP); + for (uint64_t i = 0; i < n; ++i) { + ch[i] = fnvlist_alloc(); + vdev_to_nvlist(a[i], ps, ch[i]); + } + fnvlist_add_nvlist_array(tree, "children", + (const nvlist_t * const *) ch, n); + for (uint64_t i = 0; i < n; ++i) + fnvlist_free(ch[i]); + kmem_free(ch, sizeof (nvlist_t *) * n); + } +} + +static void +iterate_vdevs(spa_t *spa, pool_scan_stat_t *ps, nvlist_t *nvl) +{ + nvlist_t *vt = fnvlist_alloc(); + vdev_t *v = spa->spa_root_vdev; + if (v == NULL) { + zfs_dbgmsg("error: NO ROOT VDEV"); + return; + } + vdev_to_nvlist(v, ps, vt); + int nspares = spa->spa_spares.sav_count; + if (nspares != 0) { + nvlist_t **sp = kmem_alloc(sizeof (nvlist_t *) * nspares, + KM_NOSLEEP); + for (int i = 0; i < nspares; i++) { + v = spa->spa_spares.sav_vdevs[i]; + sp[i] = fnvlist_alloc(); + vdev_to_nvlist(v, ps, sp[i]); + } + fnvlist_add_nvlist_array(vt, ZPOOL_CONFIG_SPARES, + (const nvlist_t * const *) sp, nspares); + for (int i = 0; i < nspares; i++) + fnvlist_free(sp[i]); + kmem_free(sp, sizeof (nvlist_t *) * nspares); + } + int nl2cache = spa->spa_l2cache.sav_count; + if (nl2cache != 0) { + nvlist_t **l2 = kmem_alloc(sizeof (nvlist_t *) * nl2cache, + KM_NOSLEEP); + for (int i = 0; i < nl2cache; i++) { + v = spa->spa_l2cache.sav_vdevs[i]; + l2[i] = fnvlist_alloc(); + vdev_to_nvlist(v, ps, l2[i]); + } + fnvlist_add_nvlist_array(vt, ZPOOL_CONFIG_L2CACHE, + (const nvlist_t * const *) l2, nl2cache); + for (int i = 0; i < nspares; i++) + fnvlist_free(l2[i]); + kmem_free(l2, sizeof (nvlist_t *) * nl2cache); + } + fnvlist_add_nvlist(nvl, "vdev_tree", vt); + fnvlist_free(vt); +} + +static const char * +pss_func_to_string(uint64_t n) +{ + const char *s = "?"; + switch (n) { + case POOL_SCAN_NONE: s = "NONE"; break; + case POOL_SCAN_SCRUB: s = "SCRUB"; break; + case POOL_SCAN_RESILVER: s = "RESILVER"; break; + case POOL_SCAN_FUNCS: s = "?"; + } + return (s); +} + +static const char *pss_state_to_string(uint64_t n) +{ + const char *s = "?"; + switch (n) { + case DSS_NONE: s = "NONE"; break; + case DSS_SCANNING: s = "SCANNING"; break; + case DSS_FINISHED: s = "FINISHED"; break; + case DSS_CANCELED: s = "CANCELED"; break; + case DSS_NUM_STATES: s = "?"; + } + return (s); +} + +static int +spa_props_json(spa_t *spa, nvlist_t **nvl) +{ + nvpair_t *curr = NULL, *item = NULL; + nvlist_t *prop; + data_type_t type; + char buf[256]; + const char *name; + uint64_t src; + + if (spa_prop_get(spa, nvl) != 0) + return (-1); + + for (curr = nvlist_next_nvpair(*nvl, NULL); curr; + curr = nvlist_next_nvpair(*nvl, curr)) { + if (nvpair_type(curr) == DATA_TYPE_NVLIST) { + prop = fnvpair_value_nvlist(curr); + for (item = nvlist_next_nvpair(prop, NULL); item; + item = nvlist_next_nvpair(prop, item)) { + name = nvpair_name(item); + type = nvpair_type(item); + if ((strcmp(name, "source") == 0) && + (type == DATA_TYPE_UINT64)) { + src = fnvpair_value_uint64(item); + memset(buf, 0, 256); + if (src & ZPROP_SRC_NONE) { + if (buf[0] != '\0') + strcat(buf, "|"); + strcat(buf, "ZPROP_SRC_NONE"); + } + if (src & ZPROP_SRC_DEFAULT) { + if (buf[0] != '\0') + strcat(buf, "|"); + strcat(buf, + "ZPROP_SRC_DEFAULT"); + } + if (src & ZPROP_SRC_TEMPORARY) { + if (buf[0] != '\0') + strcat(buf, "|"); + strcat(buf, + "ZPROP_SRC_TEMPORARY"); + } + if (src & ZPROP_SRC_INHERITED) { + if (buf[0] != '\0') + strcat(buf, "|"); + strcat(buf, + "ZPROP_SRC_INHERITED"); + } + if (src & ZPROP_SRC_RECEIVED) { + if (buf[0] != '\0') + strcat(buf, "|"); + strcat(buf, + "ZPROP_SRC_RECEIVED"); + } + fnvlist_add_string(prop, "source", buf); + } + } + } + } + return (0); +} + +/* + * Collect the spa status without any locking and return as a JSON string. + * + * Currently used by the 'zfs//stats.json' kstat. + */ +int +spa_generate_json_stats(spa_t *spa, char *buf, size_t size) +{ + int error = 0; + int ps_error = 0; + char *curr = buf; + nvlist_t *spa_config, *spa_props = NULL, *scan_stats, *nvl; + uint64_t loadtimes[2]; + pool_scan_stat_t ps; + int scl_config_lock; + + nvl = fnvlist_alloc(); + if (nvlist_dup(spa->spa_config, &spa_config, 0) != 0) { + zfs_dbgmsg("json_data: nvlist_dup failed"); + return (0); + } + fnvlist_add_nvlist(spa_config, ZPOOL_CONFIG_LOAD_INFO, + spa->spa_load_info); + + scl_config_lock = + spa_config_tryenter(spa, SCL_CONFIG, FTAG, RW_READER); + + ps_error = spa_scan_get_stats(spa, &ps); + (void) ps_error; + + if (spa_props_json(spa, &spa_props) == 0) + fnvlist_add_nvlist(spa_config, "spa_props", spa_props); + + loadtimes[0] = spa->spa_loaded_ts.tv_sec; + loadtimes[1] = spa->spa_loaded_ts.tv_nsec; + fnvlist_add_uint64_array(spa_config, ZPOOL_CONFIG_LOADED_TIME, + loadtimes, 2); + fnvlist_add_uint64(spa_config, ZPOOL_CONFIG_ERRCOUNT, + spa_approx_errlog_size(spa)); + fnvlist_add_boolean_value(spa_config, ZPOOL_CONFIG_SUSPENDED, + spa_suspended(spa)); + if (spa_suspended(spa)) { + const char *failmode; + switch (spa->spa_failmode) { + case ZIO_FAILURE_MODE_WAIT: + failmode = "wait"; + break; + case ZIO_FAILURE_MODE_CONTINUE: + failmode = "continue"; + break; + case ZIO_FAILURE_MODE_PANIC: + failmode = "panic"; + break; + default: + failmode = "???"; + } + fnvlist_add_string(spa_config, "failmode", failmode); + if (spa->spa_suspended != ZIO_SUSPEND_NONE) { + fnvlist_add_string(spa_config, + ZPOOL_CONFIG_SUSPENDED_REASON, + (spa->spa_suspended == ZIO_SUSPEND_MMP) ? + "MMP" : "IO"); + } + } + + fnvlist_add_uint32(nvl, "status_json_version", JSON_STATUS_VERSION); + fnvlist_add_boolean_value(nvl, "scl_config_lock", scl_config_lock != 0); + fnvlist_add_uint32(nvl, "scan_error", ps_error); + + scan_stats = fnvlist_alloc(); + if (ps_error == 0) { + fnvlist_add_string(scan_stats, "func", + pss_func_to_string(ps.pss_func)); + fnvlist_add_string(scan_stats, "state", + pss_state_to_string(ps.pss_state)); + fnvlist_add_uint64(scan_stats, "start_time", ps.pss_start_time); + fnvlist_add_uint64(scan_stats, "end_time", ps.pss_end_time); + fnvlist_add_uint64(scan_stats, "to_examine", ps.pss_to_examine); + fnvlist_add_uint64(scan_stats, "examined", ps.pss_examined); + fnvlist_add_uint64(scan_stats, "processed", ps.pss_processed); + fnvlist_add_uint64(scan_stats, "errors", ps.pss_errors); + fnvlist_add_uint64(scan_stats, "pass_exam", ps.pss_pass_exam); + fnvlist_add_uint64(scan_stats, "pass_start", ps.pss_pass_start); + fnvlist_add_uint64(scan_stats, "pass_scrub_pause", + ps.pss_pass_scrub_pause); + fnvlist_add_uint64(scan_stats, "pass_scrub_spent_paused", + ps.pss_pass_scrub_spent_paused); + fnvlist_add_uint64(scan_stats, "pass_issued", + ps.pss_pass_issued); + fnvlist_add_uint64(scan_stats, "issued", ps.pss_issued); + } else if (ps_error == ENOENT) { + fnvlist_add_string(scan_stats, "func", "NONE"); + fnvlist_add_string(scan_stats, "state", "NONE"); + } else { + fnvlist_add_string(scan_stats, "func", "NONE"); + fnvlist_add_string(scan_stats, "state", "NONE"); + } + fnvlist_add_nvlist(nvl, "scan_stats", scan_stats); + fnvlist_add_string(nvl, "state", spa_state_to_name(spa)); + + fnvlist_remove(spa_config, "state"); + spa_add_spares(spa, spa_config); + spa_add_l2cache(spa, spa_config); + spa_add_feature_stats(spa, spa_config); + + /* add spa_config to output nvlist */ + fnvlist_merge(nvl, spa_config); + iterate_vdevs(spa, &ps, nvl); + + if (scl_config_lock) + spa_config_exit(spa, SCL_CONFIG, FTAG); + + error = nvlist_to_json(nvl, &curr, size); + nvlist_free(nvl); + nvlist_free(spa_config); + nvlist_free(spa_props); + nvlist_free(scan_stats); + + return (error); +} diff --git a/module/zfs/spa_stats.c b/module/zfs/spa_stats.c index 17ed2a620b..01bdb32c23 100644 --- a/module/zfs/spa_stats.c +++ b/module/zfs/spa_stats.c @@ -24,6 +24,7 @@ #include #include #include +#include /* * Keeps stats on last N reads per spa_t, disabled by default. @@ -988,6 +989,52 @@ spa_iostats_destroy(spa_t *spa) mutex_destroy(&shk->lock); } +static void * +spa_json_addr(kstat_t *ksp, loff_t n) +{ + if (n == 0) + return (ksp->ks_private); + return (NULL); +} + +static int +spa_json_data(char *buf, size_t size, void *data) +{ + spa_t *spa = (spa_t *)data; + + return (spa_generate_json_stats(spa, buf, size)); +} + +static void +spa_json_stats_init(spa_t *spa) +{ + char *name; + kstat_t *ksp; + + mutex_init(&spa->spa_json_stats.lock, NULL, MUTEX_DEFAULT, NULL); + name = kmem_asprintf("zfs/%s", spa_name(spa)); + ksp = kstat_create(name, 0, "status.json", "misc", KSTAT_TYPE_RAW, 0, + KSTAT_FLAG_VIRTUAL | KSTAT_FLAG_RESTRICTED | KSTAT_FLAG_NO_HEADERS); + spa->spa_json_stats.kstat = ksp; + if (ksp) { + ksp->ks_lock = &spa->spa_json_stats.lock; + ksp->ks_data = NULL; + ksp->ks_private = spa; + kstat_set_raw_ops(ksp, NULL, spa_json_data, spa_json_addr); + kstat_install(ksp); + } + + kmem_strfree(name); +} + +static void +spa_json_stats_destroy(spa_t *spa) +{ + if (spa->spa_json_stats.kstat) + kstat_delete(spa->spa_json_stats.kstat); + mutex_destroy(&spa->spa_json_stats.lock); +} + void spa_stats_init(spa_t *spa) { @@ -998,11 +1045,13 @@ spa_stats_init(spa_t *spa) spa_state_init(spa); spa_guid_init(spa); spa_iostats_init(spa); + spa_json_stats_init(spa); } void spa_stats_destroy(spa_t *spa) { + spa_json_stats_destroy(spa); spa_iostats_destroy(spa); spa_health_destroy(spa); spa_tx_assign_destroy(spa); diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 088e46ce57..874802d3cf 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -559,7 +559,8 @@ tests = ['zpool_status_001_pos', 'zpool_status_002_pos', 'zpool_status_003_pos', 'zpool_status_004_pos', 'zpool_status_005_pos', 'zpool_status_006_pos', 'zpool_status_007_pos', 'zpool_status_008_pos', - 'zpool_status_features_001_pos'] + 'zpool_status_features_001_pos', + 'zpool_status_kstat_pos'] tags = ['functional', 'cli_root', 'zpool_status'] [tests/functional/cli_root/zpool_sync] diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index bbeabc6dfb..10d87d6a08 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -1256,6 +1256,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/cli_root/zpool_status/zpool_status_007_pos.ksh \ functional/cli_root/zpool_status/zpool_status_008_pos.ksh \ functional/cli_root/zpool_status/zpool_status_features_001_pos.ksh \ + functional/cli_root/zpool_status/zpool_status_kstat_pos.ksh \ functional/cli_root/zpool_sync/cleanup.ksh \ functional/cli_root/zpool_sync/setup.ksh \ functional/cli_root/zpool_sync/zpool_sync_001_pos.ksh \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_kstat_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_kstat_pos.ksh new file mode 100755 index 0000000000..759c3ec578 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_kstat_pos.ksh @@ -0,0 +1,66 @@ +#!/bin/ksh -p + +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2024 Klara +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# Verify the status.json kstat +# +# STRATEGY: +# 1. Create zpool +# 2. Confirm the output of the kstat is valid json +# 3. Confirm that some expected keys are present +# + +function cleanup +{ + poolexists $TESTPOOL2 && destroy_pool $TESTPOOL2 + log_must rm -f $all_vdevs + [[ -f $tmpfile ]] && rm -f $tmpfile +} + +log_assert "Verify status.json kstat" + +log_onexit cleanup + +all_vdevs=$(echo $TESTDIR/vdev{1..6}) +log_must mkdir -p $TESTDIR +log_must truncate -s $MINVDEVSIZE $all_vdevs +tmpfile=$TEST_BASE_DIR/tmpfile.$$ + +for raid_type in "draid2:3d:6c:1s" "raidz2"; do + + log_must zpool create -f $TESTPOOL2 $raid_type $all_vdevs + + # Verify that the JSON output is valid + log_must eval "kstat ${TESTPOOL2}/status.json | python3 -m json.tool > $tmpfile" + + # Verify that some of the expected keys are present + log_must eval "grep '\"vdev_children\": 6' $tmpfile" + log_must eval "grep '\"nparity\": 2' $tmpfile" + log_must eval "grep '\"state\": \"ONLINE\"' $tmpfile" + log_must eval "grep '\"name\": \"$TESTPOOL2\"' $tmpfile" + + zpool destroy $TESTPOOL2 +done + +log_pass "Verify status.json kstat"