diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 665e183485f0..4fbc475bad90 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -83,6 +83,7 @@ #include "zfs_util.h" #include "zfs_comutil.h" #include "zfs_projectutil.h" +#include "json.h" libzfs_handle_t *g_zfs; @@ -1918,18 +1919,11 @@ is_recvd_column(zprop_get_cbdata_t *cbp) static nvlist_t * zfs_json_schema(int maj_v, int min_v) { - nvlist_t *sch = NULL; - nvlist_t *ov = NULL; char cmd[MAX_CMD_LEN]; snprintf(cmd, MAX_CMD_LEN, "zfs %s", current_command->name); - sch = fnvlist_alloc(); - ov = fnvlist_alloc(); - fnvlist_add_string(ov, "command", cmd); - fnvlist_add_uint32(ov, "vers_major", maj_v); - fnvlist_add_uint32(ov, "vers_minor", min_v); - fnvlist_add_nvlist(sch, "output_version", ov); - fnvlist_free(ov); + nvlist_t *sch = fnvlist_alloc(); + json_add_output_version(sch, cmd, maj_v, min_v); return (sch); } diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 8eb7e35cf2d0..812191607ba0 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -77,6 +77,8 @@ #include "zfs_comutil.h" #include "zfeature_common.h" #include "zfs_valstr.h" +#include "json.h" +#include "literals.h" #include "statcommon.h" @@ -310,41 +312,6 @@ static const char *checkpoint_state_str[] = { "DISCARDING" }; -static const char *vdev_state_str[] = { - "UNKNOWN", - "CLOSED", - "OFFLINE", - "REMOVED", - "CANT_OPEN", - "FAULTED", - "DEGRADED", - "ONLINE" -}; - -static const char *vdev_aux_str[] = { - "NONE", - "OPEN_FAILED", - "CORRUPT_DATA", - "NO_REPLICAS", - "BAD_GUID_SUM", - "TOO_SMALL", - "BAD_LABEL", - "VERSION_NEWER", - "VERSION_OLDER", - "UNSUP_FEAT", - "SPARED", - "ERR_EXCEEDED", - "IO_FAILURE", - "BAD_LOG", - "EXTERNAL", - "SPLIT_POOL", - "BAD_ASHIFT", - "EXTERNAL_PERSIST", - "ACTIVE", - "CHILDREN_OFFLINE", - "ASHIFT_TOO_BIG" -}; - static const char *vdev_init_state_str[] = { "NONE", "ACTIVE", @@ -1090,15 +1057,10 @@ static nvlist_t * zpool_json_schema(int maj_v, int min_v) { char cmd[MAX_CMD_LEN]; - nvlist_t *sch = fnvlist_alloc(); - nvlist_t *ov = fnvlist_alloc(); - snprintf(cmd, MAX_CMD_LEN, "zpool %s", current_command->name); - fnvlist_add_string(ov, "command", cmd); - fnvlist_add_uint32(ov, "vers_major", maj_v); - fnvlist_add_uint32(ov, "vers_minor", min_v); - fnvlist_add_nvlist(sch, "output_version", ov); - fnvlist_free(ov); + + nvlist_t *sch = fnvlist_alloc(); + json_add_output_version(sch, cmd, maj_v, min_v); return (sch); } @@ -1248,7 +1210,7 @@ fill_vdev_info(nvlist_t *list, zpool_handle_t *zhp, char *name, if (nvlist_lookup_uint64_array(nvdev, ZPOOL_CONFIG_VDEV_STATS, (uint64_t **)&vs, &c) == 0) { fnvlist_add_string(list, "state", - vdev_state_str[vs->vs_state]); + vdev_state_string(vs->vs_state)); } } } @@ -9276,7 +9238,7 @@ vdev_stats_nvlist(zpool_handle_t *zhp, status_cbdata_t *cb, nvlist_t *nv, fnvlist_add_string(vds, "was", fnvlist_lookup_string(nv, ZPOOL_CONFIG_PATH)); } else if (vs->vs_aux != VDEV_AUX_NONE) { - fnvlist_add_string(vds, "aux", vdev_aux_str[vs->vs_aux]); + fnvlist_add_string(vds, "aux", vdev_aux_string(vs->vs_aux)); } else if (children == 0 && !isspare && getenv("ZPOOL_STATUS_NON_NATIVE_ASHIFT_IGNORE") == NULL && VDEV_STAT_VALID(vs_physical_ashift, vsc) && diff --git a/include/Makefile.am b/include/Makefile.am index a9258deabfd7..f42757cdcdf7 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -8,6 +8,8 @@ endif COMMON_H = \ cityhash.h \ + json.h \ + literals.h \ zfeature_common.h \ zfs_comutil.h \ zfs_deleg.h \ @@ -175,6 +177,7 @@ COMMON_H = \ KERNEL_H = \ + sys/spa_stats_json.h \ sys/zfs_ioctl.h \ sys/zfs_ioctl_impl.h \ sys/zfs_onexit.h \ diff --git a/include/json.h b/include/json.h new file mode 100644 index 000000000000..69e38446d336 --- /dev/null +++ b/include/json.h @@ -0,0 +1,43 @@ +// 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. + */ + +#ifndef _JSON_H +#define _JSON_H extern __attribute__((visibility("default"))) + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +_JSON_H void json_add_output_version(nvlist_t *nvl, const char *cmd, int maj_v, + int min_v); + +#ifdef __cplusplus +} +#endif + +#endif /* _JSON_H */ diff --git a/include/literals.h b/include/literals.h new file mode 100644 index 000000000000..31d385e9292c --- /dev/null +++ b/include/literals.h @@ -0,0 +1,43 @@ +// 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. + */ + +#ifndef _LITERALS_H +#define _LITERALS_H extern __attribute__((visibility("default"))) + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +_LITERALS_H const char *vdev_state_string(uint64_t state); +_LITERALS_H const char *vdev_aux_string(uint64_t aux); + +#ifdef __cplusplus +} +#endif + +#endif /* _LITERALS_H */ diff --git a/include/sys/nvpair.h b/include/sys/nvpair.h index 6bec9702eb16..6da74059d355 100644 --- a/include/sys/nvpair.h +++ b/include/sys/nvpair.h @@ -415,6 +415,18 @@ _SYS_NVPAIR_H uint64_t fnvpair_value_uint64(const nvpair_t *nvp); _SYS_NVPAIR_H const char *fnvpair_value_string(const nvpair_t *nvp); _SYS_NVPAIR_H nvlist_t *fnvpair_value_nvlist(nvpair_t *nvp); +/* JSON generation */ + +typedef int (*nvjson_writer_t)(void *, const char *); +typedef struct nvjson { + char *buf; + size_t size; + nvjson_writer_t writer; + void *writer_ctx; + int (*str_handler)(const char *, nvjson_writer_t, void *); +} nvjson_t; +_SYS_NVPAIR_H int nvlist_to_json(nvjson_t *, nvlist_t *); + #ifdef __cplusplus } #endif diff --git a/include/sys/spa_impl.h b/include/sys/spa_impl.h index 8c52f751a819..5384bbb694c7 100644 --- a/include/sys/spa_impl.h +++ b/include/sys/spa_impl.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -437,6 +438,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_stats_json_t spa_stats_json; /* 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_stats_json.h b/include/sys/spa_stats_json.h new file mode 100644 index 000000000000..c3226b3b883c --- /dev/null +++ b/include/sys/spa_stats_json.h @@ -0,0 +1,48 @@ +// 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 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_STATS_JSON_H +#define _SYS_SPA_STATS_JSON_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct spa_stats_json { + kmutex_t ssj_lock; + kstat_t *ssj_kstat; +} spa_stats_json_t; + +extern int spa_stats_json_generate(spa_t *spa, char *buf, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_SPA_STATS_JSON_H */ diff --git a/lib/libnvpair/Makefile.am b/lib/libnvpair/Makefile.am index 87b8d32aa175..fd353a8d9203 100644 --- a/lib/libnvpair/Makefile.am +++ b/lib/libnvpair/Makefile.am @@ -17,6 +17,7 @@ dist_libnvpair_la_SOURCES = \ nodist_libnvpair_la_SOURCES = \ module/nvpair/nvpair_alloc_fixed.c \ module/nvpair/nvpair.c \ + module/nvpair/nvpair_json.c \ module/nvpair/fnvpair.c libnvpair_la_LIBADD = \ diff --git a/lib/libnvpair/libnvpair.abi b/lib/libnvpair/libnvpair.abi index a7f51f49487d..5348bb9bc555 100644 --- a/lib/libnvpair/libnvpair.abi +++ b/lib/libnvpair/libnvpair.abi @@ -197,6 +197,7 @@ + @@ -1880,6 +1881,25 @@ + + + + + + + + + + + + + + + + + + + @@ -1899,8 +1919,11 @@ + + + @@ -1974,6 +1997,17 @@ + + + + + + + + + + + @@ -3440,4 +3474,11 @@ + + + + + + + diff --git a/lib/libnvpair/libnvpair_json.c b/lib/libnvpair/libnvpair_json.c index 6ed984698b59..d9529ba398c4 100644 --- a/lib/libnvpair/libnvpair_json.c +++ b/lib/libnvpair/libnvpair_json.c @@ -12,21 +12,25 @@ /* * Copyright (c) 2014, Joyent, Inc. * Copyright (c) 2017 by Delphix. All rights reserved. + * Copyright (c) 2025, Klara, Inc. */ #include #include #include #include -#include #include "libnvpair.h" -#define FPRINTF(fp, ...) \ - do { \ - if (fprintf(fp, __VA_ARGS__) < 0) \ - return (-1); \ - } while (0) +static int +nvjson_file_writer(void *context, const char *str) +{ + FILE *fd = context; + int ret = fputs(str, fd); + if (ret < 0) + return (ret); + return (0); +} /* * When formatting a string for JSON output we must escape certain characters, @@ -45,14 +49,23 @@ * representable Unicode characters included in their escaped numeric form. */ static int -nvlist_print_json_string(FILE *fp, const char *input) +nvjson_singlebyte_str_handler(const char *str, nvjson_writer_t w, void *wctx) { +#define W(x) \ + do { \ + int ret = w(wctx, (x)); \ + if (ret != 0) \ + return (ret); \ + } while (0) + + char tmp[8]; + mbstate_t mbr = {0}; wchar_t c; size_t sz; - FPRINTF(fp, "\""); - while ((sz = mbrtowc(&c, input, MB_CUR_MAX, &mbr)) > 0) { + W("\""); + while ((sz = mbrtowc(&c, str, MB_CUR_MAX, &mbr)) > 0) { if (sz == (size_t)-1 || sz == (size_t)-2) { /* * We last read an invalid multibyte character sequence, @@ -62,25 +75,25 @@ nvlist_print_json_string(FILE *fp, const char *input) } switch (c) { case '"': - FPRINTF(fp, "\\\""); + W("\\\""); break; case '\n': - FPRINTF(fp, "\\n"); + W("\\n"); break; case '\r': - FPRINTF(fp, "\\r"); + W("\\r"); break; case '\\': - FPRINTF(fp, "\\\\"); + W("\\\\"); break; case '\f': - FPRINTF(fp, "\\f"); + W("\\f"); break; case '\t': - FPRINTF(fp, "\\t"); + W("\\t"); break; case '\b': - FPRINTF(fp, "\\b"); + W("\\b"); break; default: if ((c >= 0x00 && c <= 0x1f) || @@ -90,20 +103,24 @@ nvlist_print_json_string(FILE *fp, const char *input) * characters in the Basic Multilingual Plane * as JSON-escaped multibyte characters. */ - FPRINTF(fp, "\\u%04x", (int)(0xffff & c)); + snprintf(tmp, sizeof (tmp), "\\u%04x", + (int)(0xffff & c)); + W(tmp); } else if (c >= 0x20 && c <= 0x7f) { /* * Render other 7-bit ASCII characters directly * and drop other, unrepresentable characters. */ - FPRINTF(fp, "%c", (int)(0xff & c)); + snprintf(tmp, sizeof (tmp), "%c", + (int)(0xff & c)); + W(tmp); } break; } - input += sz; + str += sz; } - FPRINTF(fp, "\""); + W("\""); return (0); } @@ -115,290 +132,12 @@ nvlist_print_json_string(FILE *fp, const char *input) int nvlist_print_json(FILE *fp, nvlist_t *nvl) { - nvpair_t *curr; - boolean_t first = B_TRUE; - - FPRINTF(fp, "{"); - - for (curr = nvlist_next_nvpair(nvl, NULL); curr; - curr = nvlist_next_nvpair(nvl, curr)) { - data_type_t type = nvpair_type(curr); - - if (!first) - FPRINTF(fp, ","); - else - first = B_FALSE; - - if (nvlist_print_json_string(fp, nvpair_name(curr)) == -1) - return (-1); - FPRINTF(fp, ":"); - - switch (type) { - case DATA_TYPE_STRING: { - const char *string = fnvpair_value_string(curr); - if (nvlist_print_json_string(fp, string) == -1) - return (-1); - break; - } - - case DATA_TYPE_BOOLEAN: { - FPRINTF(fp, "true"); - break; - } - - case DATA_TYPE_BOOLEAN_VALUE: { - FPRINTF(fp, "%s", fnvpair_value_boolean_value(curr) == - B_TRUE ? "true" : "false"); - break; - } - - case DATA_TYPE_BYTE: { - FPRINTF(fp, "%hhu", fnvpair_value_byte(curr)); - break; - } - - case DATA_TYPE_INT8: { - FPRINTF(fp, "%hhd", fnvpair_value_int8(curr)); - break; - } - - case DATA_TYPE_UINT8: { - FPRINTF(fp, "%hhu", fnvpair_value_uint8(curr)); - break; - } - - case DATA_TYPE_INT16: { - FPRINTF(fp, "%hd", fnvpair_value_int16(curr)); - break; - } - - case DATA_TYPE_UINT16: { - FPRINTF(fp, "%hu", fnvpair_value_uint16(curr)); - break; - } - - case DATA_TYPE_INT32: { - FPRINTF(fp, "%d", fnvpair_value_int32(curr)); - break; - } - - case DATA_TYPE_UINT32: { - FPRINTF(fp, "%u", fnvpair_value_uint32(curr)); - break; - } - - case DATA_TYPE_INT64: { - FPRINTF(fp, "%lld", - (long long)fnvpair_value_int64(curr)); - break; - } - - case DATA_TYPE_UINT64: { - FPRINTF(fp, "%llu", - (unsigned long long)fnvpair_value_uint64(curr)); - break; - } - - case DATA_TYPE_HRTIME: { - hrtime_t val; - VERIFY0(nvpair_value_hrtime(curr, &val)); - FPRINTF(fp, "%llu", (unsigned long long)val); - break; - } - - case DATA_TYPE_DOUBLE: { - double val; - VERIFY0(nvpair_value_double(curr, &val)); - FPRINTF(fp, "%f", val); - break; - } - - case DATA_TYPE_NVLIST: { - if (nvlist_print_json(fp, - fnvpair_value_nvlist(curr)) == -1) - return (-1); - break; - } - - case DATA_TYPE_STRING_ARRAY: { - const char **val; - uint_t valsz, i; - VERIFY0(nvpair_value_string_array(curr, &val, &valsz)); - FPRINTF(fp, "["); - for (i = 0; i < valsz; i++) { - if (i > 0) - FPRINTF(fp, ","); - if (nvlist_print_json_string(fp, val[i]) == -1) - return (-1); - } - FPRINTF(fp, "]"); - break; - } - - case DATA_TYPE_NVLIST_ARRAY: { - nvlist_t **val; - uint_t valsz, i; - VERIFY0(nvpair_value_nvlist_array(curr, &val, &valsz)); - FPRINTF(fp, "["); - for (i = 0; i < valsz; i++) { - if (i > 0) - FPRINTF(fp, ","); - if (nvlist_print_json(fp, val[i]) == -1) - return (-1); - } - FPRINTF(fp, "]"); - break; - } - - case DATA_TYPE_BOOLEAN_ARRAY: { - boolean_t *val; - uint_t valsz, i; - VERIFY0(nvpair_value_boolean_array(curr, &val, &valsz)); - FPRINTF(fp, "["); - for (i = 0; i < valsz; i++) { - if (i > 0) - FPRINTF(fp, ","); - FPRINTF(fp, val[i] == B_TRUE ? - "true" : "false"); - } - FPRINTF(fp, "]"); - break; - } - - case DATA_TYPE_BYTE_ARRAY: { - uchar_t *val; - uint_t valsz, i; - VERIFY0(nvpair_value_byte_array(curr, &val, &valsz)); - FPRINTF(fp, "["); - for (i = 0; i < valsz; i++) { - if (i > 0) - FPRINTF(fp, ","); - FPRINTF(fp, "%hhu", val[i]); - } - FPRINTF(fp, "]"); - break; - } - - case DATA_TYPE_UINT8_ARRAY: { - uint8_t *val; - uint_t valsz, i; - VERIFY0(nvpair_value_uint8_array(curr, &val, &valsz)); - FPRINTF(fp, "["); - for (i = 0; i < valsz; i++) { - if (i > 0) - FPRINTF(fp, ","); - FPRINTF(fp, "%hhu", val[i]); - } - FPRINTF(fp, "]"); - break; - } - - case DATA_TYPE_INT8_ARRAY: { - int8_t *val; - uint_t valsz, i; - VERIFY0(nvpair_value_int8_array(curr, &val, &valsz)); - FPRINTF(fp, "["); - for (i = 0; i < valsz; i++) { - if (i > 0) - FPRINTF(fp, ","); - FPRINTF(fp, "%hhd", val[i]); - } - FPRINTF(fp, "]"); - break; - } - - case DATA_TYPE_UINT16_ARRAY: { - uint16_t *val; - uint_t valsz, i; - VERIFY0(nvpair_value_uint16_array(curr, &val, &valsz)); - FPRINTF(fp, "["); - for (i = 0; i < valsz; i++) { - if (i > 0) - FPRINTF(fp, ","); - FPRINTF(fp, "%hu", val[i]); - } - FPRINTF(fp, "]"); - break; - } - - case DATA_TYPE_INT16_ARRAY: { - int16_t *val; - uint_t valsz, i; - VERIFY0(nvpair_value_int16_array(curr, &val, &valsz)); - FPRINTF(fp, "["); - for (i = 0; i < valsz; i++) { - if (i > 0) - FPRINTF(fp, ","); - FPRINTF(fp, "%hd", val[i]); - } - FPRINTF(fp, "]"); - break; - } - - case DATA_TYPE_UINT32_ARRAY: { - uint32_t *val; - uint_t valsz, i; - VERIFY0(nvpair_value_uint32_array(curr, &val, &valsz)); - FPRINTF(fp, "["); - for (i = 0; i < valsz; i++) { - if (i > 0) - FPRINTF(fp, ","); - FPRINTF(fp, "%u", val[i]); - } - FPRINTF(fp, "]"); - break; - } - - case DATA_TYPE_INT32_ARRAY: { - int32_t *val; - uint_t valsz, i; - VERIFY0(nvpair_value_int32_array(curr, &val, &valsz)); - FPRINTF(fp, "["); - for (i = 0; i < valsz; i++) { - if (i > 0) - FPRINTF(fp, ","); - FPRINTF(fp, "%d", val[i]); - } - FPRINTF(fp, "]"); - break; - } - - case DATA_TYPE_UINT64_ARRAY: { - uint64_t *val; - uint_t valsz, i; - VERIFY0(nvpair_value_uint64_array(curr, &val, &valsz)); - FPRINTF(fp, "["); - for (i = 0; i < valsz; i++) { - if (i > 0) - FPRINTF(fp, ","); - FPRINTF(fp, "%llu", - (unsigned long long)val[i]); - } - FPRINTF(fp, "]"); - break; - } - - case DATA_TYPE_INT64_ARRAY: { - int64_t *val; - uint_t valsz, i; - VERIFY0(nvpair_value_int64_array(curr, &val, &valsz)); - FPRINTF(fp, "["); - for (i = 0; i < valsz; i++) { - if (i > 0) - FPRINTF(fp, ","); - FPRINTF(fp, "%lld", (long long)val[i]); - } - FPRINTF(fp, "]"); - break; - } - - case DATA_TYPE_UNKNOWN: - case DATA_TYPE_DONTCARE: - return (-1); - } - - } - - FPRINTF(fp, "}"); - return (0); + nvjson_t nvjson = { + .buf = NULL, + .size = 0, + .writer = nvjson_file_writer, + .writer_ctx = fp, + .str_handler = nvjson_singlebyte_str_handler + }; + return (nvlist_to_json(&nvjson, nvl)); } diff --git a/lib/libzfs/Makefile.am b/lib/libzfs/Makefile.am index 5f8963dccd1a..ca6f54c56d5c 100644 --- a/lib/libzfs/Makefile.am +++ b/lib/libzfs/Makefile.am @@ -35,6 +35,8 @@ endif nodist_libzfs_la_SOURCES = \ module/zcommon/cityhash.c \ + module/zcommon/json.c \ + module/zcommon/literals.c \ module/zcommon/zfeature_common.c \ module/zcommon/zfs_comutil.c \ module/zcommon/zfs_deleg.c \ diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index 26ae5d2f1396..3632eb0ab9e6 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -195,6 +195,7 @@ + @@ -270,6 +271,7 @@ + @@ -286,6 +288,7 @@ + @@ -9362,6 +9365,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/lib/libzpool/Makefile.am b/lib/libzpool/Makefile.am index 829d626d4866..5a8853a54029 100644 --- a/lib/libzpool/Makefile.am +++ b/lib/libzpool/Makefile.am @@ -49,6 +49,8 @@ nodist_libzpool_la_SOURCES = \ module/os/linux/zfs/zio_crypt.c \ \ module/zcommon/cityhash.c \ + module/zcommon/json.c \ + module/zcommon/literals.c \ module/zcommon/simd_stat.c \ module/zcommon/zfeature_common.c \ module/zcommon/zfs_comutil.c \ @@ -134,6 +136,7 @@ nodist_libzpool_la_SOURCES = \ module/zfs/spa_log_spacemap.c \ module/zfs/spa_misc.c \ module/zfs/spa_stats.c \ + module/zfs/spa_stats_json.c \ module/zfs/space_map.c \ module/zfs/space_reftree.c \ module/zfs/txg.c \ diff --git a/man/man4/zfs.4 b/man/man4/zfs.4 index 40cb3ec5ab3b..c6faf5ab7c12 100644 --- a/man/man4/zfs.4 +++ b/man/man4/zfs.4 @@ -296,6 +296,15 @@ Default DDT ZAP indirect block size as a power of 2. Note that changing this after creating a DDT on the pool will not affect existing DDTs, only newly created ones. . +.It Sy zfs_lockless_read_enabled Ns = Ns Sy 0 Ns | Ns 1 Pq uint +Enables lockless traversal of kernel structures in emergencies +when reading pool status via the +.Pa /proc/spl/kstat/zfs/ Ns Ao Ar pool Ac Ns Pa /status_json . +It may proceed even if required read locks are not held. +Potential consequences, such as a kernel panic, must be considered before use. +For instance, a debug build of a kernel will panic due to lock assertions. +The default is 0. +. .It Sy zfs_default_bs Ns = Ns Sy 9 Po 512 B Pc Pq int Default dnode block size as a power of 2. . diff --git a/module/Kbuild.in b/module/Kbuild.in index edb475c7d3e0..e833db713416 100644 --- a/module/Kbuild.in +++ b/module/Kbuild.in @@ -218,7 +218,8 @@ NVPAIR_OBJS := \ fnvpair.o \ nvpair.o \ nvpair_alloc_fixed.o \ - nvpair_alloc_spl.o + nvpair_alloc_spl.o \ + nvpair_json.o zfs-objs += $(addprefix nvpair/,$(NVPAIR_OBJS)) @@ -231,6 +232,8 @@ zfs-objs += $(addprefix unicode/,$(UNICODE_OBJS)) ZCOMMON_OBJS := \ cityhash.o \ + json.o \ + literals.o \ simd_stat.o \ zfeature_common.o \ zfs_comutil.o \ @@ -377,6 +380,7 @@ ZFS_OBJS := \ spa_log_spacemap.o \ spa_misc.o \ spa_stats.o \ + spa_stats_json.o \ space_map.o \ space_reftree.o \ txg.o \ diff --git a/module/Makefile.bsd b/module/Makefile.bsd index 7e7c3db73a43..ad3a6ace8c85 100644 --- a/module/Makefile.bsd +++ b/module/Makefile.bsd @@ -174,7 +174,8 @@ SRCS+= lapi.c \ SRCS+= fnvpair.c \ nvpair.c \ nvpair_alloc_fixed.c \ - nvpair_alloc_spl.c + nvpair_alloc_spl.c \ + nvpair_json.c #os/freebsd/spl SRCS+= acl_common.c \ @@ -234,6 +235,8 @@ SRCS+= u8_textprep.c #zcommon SRCS+= cityhash.c \ + json.c \ + literals.c \ zfeature_common.c \ zfs_comutil.c \ zfs_deleg.c \ @@ -320,6 +323,7 @@ SRCS+= abd.c \ spa_log_spacemap.c \ spa_misc.c \ spa_stats.c \ + spa_stats_json.c \ txg.c \ uberblock.c \ unique.c \ diff --git a/module/nvpair/nvpair_json.c b/module/nvpair/nvpair_json.c new file mode 100644 index 000000000000..67eca12d27ed --- /dev/null +++ b/module/nvpair/nvpair_json.c @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: CDDL-1.0 +/* + * 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. + */ +/* + * Copyright (c) 2014, Joyent, Inc. + * Copyright (c) 2017 by Delphix. All rights reserved. + * Copyright (c) 2025, Klara, Inc. + */ + +/* + * General logic for JSON preparation and generation used by both user and + * kernel lands. + */ + +#include +#include + +#if defined(_KERNEL) +#include +#else +#include +#include +#endif + +typedef struct nvjson_context { + nvjson_t *r; /* request */ + char *p; + char tmp[32]; +} nvjson_context_t; + +static int +nvjson_printf(nvjson_context_t *ctx, const char *fmt, ...) +{ + int ret; + va_list va; + + if (ctx->p == NULL) { + /* to the temporary buffer */ + va_start(va, fmt); + ret = vsnprintf(ctx->tmp, sizeof (ctx->tmp), fmt, va); + va_end(va); + if (ret < 0) + return (ENOMEM); + if (ret >= sizeof (ctx->tmp)) + return (EFBIG); + if (ctx->r->writer != NULL) { + ret = ctx->r->writer(ctx->r->writer_ctx, ctx->tmp); + if (ret != 0) + return (ret); + } + } else { + /* directly to the provided buffer */ + if (ctx->p - ctx->r->buf >= ctx->r->size) + return (ENOMEM); + va_start(va, fmt); + ret = vsnprintf(ctx->p, ctx->r->size - (ctx->p - ctx->r->buf), + fmt, va); + va_end(va); + if (ret < 0) + return (EFBIG); + if (ret >= (ctx->r->buf + ctx->r->size - ctx->p)) + return (ENOMEM); + if (ctx->r->writer != NULL) { + ret = ctx->r->writer(ctx->r->writer_ctx, ctx->p); + if (ret != 0) + return (ret); + } + ctx->p += ret; + } + + return (0); +} + +#define PRINTF(ctx, ...) \ + do { \ + int ret = nvjson_printf(ctx, __VA_ARGS__); \ + if (ret != 0) \ + return (ret); \ + } while (0) + +static int +nvjson_default_writer(void *context, const char *str) +{ + return (nvjson_printf((nvjson_context_t *)context, str)); +} + +/* Multibyte chars are written as is. */ +static int +nvjson_default_str_handler(const char *str, nvjson_writer_t w, void *wctx) +{ +#define W(x) \ + do { \ + int ret = w(wctx, (x)); \ + if (ret != 0) \ + return (ret); \ + } while (0) + + char tmp[8]; + int c; + + if (str == NULL) { + W("null"); + return (0); + } + + W("\""); + while (*str) { + switch (*str) { + case '"': + W("\\\""); + break; + case '\n': + W("\\n"); + break; + case '\r': + W("\\r"); + break; + case '\\': + W("\\\\"); + break; + case '\f': + W("\\f"); + break; + case '\t': + W("\\t"); + break; + case '\b': + W("\\b"); + break; + default: + c = (int)*str; + if (c >= 0x00 && c <= 0x1f) + snprintf(tmp, sizeof (tmp), "\\u%04x", *str); + else + snprintf(tmp, sizeof (tmp), "%c", *str); + W(tmp); + } + str++; + } + W("\""); + + return (0); +} + +static int +nvjson_print_string(nvjson_context_t *ctx, const char *str) +{ + nvjson_writer_t w; + void *wctx; + + if (ctx->r->writer == NULL) { + w = nvjson_default_writer; + wctx = ctx; + } else { + w = ctx->r->writer; + wctx = ctx->r->writer_ctx; + } + + if (ctx->r->str_handler == NULL) + return (nvjson_default_str_handler(str, w, wctx)); + else + return (ctx->r->str_handler(str, w, wctx)); +} + +#define PRINT_STRING(ctx, str) \ + do { \ + int ret = nvjson_print_string(ctx, str); \ + if (ret != 0) \ + return (ret); \ + } while (0) + +static int +nvlist_to_json_impl(nvjson_context_t *ctx, nvlist_t *nvl) +{ + PRINTF(ctx, "{"); + + nvpair_t *nv; + boolean_t first = B_TRUE; + int ret; + + nv = nvlist_next_nvpair(nvl, NULL); + for (; nv; nv = nvlist_next_nvpair(nvl, nv)) { + if (first) + first = B_FALSE; + else + PRINTF(ctx, ","); + + PRINT_STRING(ctx, nvpair_name(nv)); + PRINTF(ctx, ":"); + + switch (nvpair_type(nv)) { + case DATA_TYPE_STRING: + PRINT_STRING(ctx, fnvpair_value_string(nv)); + break; + case DATA_TYPE_BOOLEAN: + PRINTF(ctx, "true"); + break; + case DATA_TYPE_BOOLEAN_VALUE: + PRINTF(ctx, fnvpair_value_boolean_value(nv) == B_TRUE + ? "true" : "false"); + break; + case DATA_TYPE_BYTE: + PRINTF(ctx, "%hhu", fnvpair_value_byte(nv)); + break; + case DATA_TYPE_INT8: + PRINTF(ctx, "%hhd", fnvpair_value_int8(nv)); + break; + case DATA_TYPE_UINT8: + PRINTF(ctx, "%hhu", fnvpair_value_uint8(nv)); + break; + case DATA_TYPE_INT16: + PRINTF(ctx, "%hd", fnvpair_value_int16(nv)); + break; + case DATA_TYPE_UINT16: + PRINTF(ctx, "%hu", fnvpair_value_uint16(nv)); + break; + case DATA_TYPE_INT32: + PRINTF(ctx, "%d", fnvpair_value_int32(nv)); + break; + case DATA_TYPE_UINT32: + PRINTF(ctx, "%u", fnvpair_value_uint32(nv)); + break; + case DATA_TYPE_INT64: + PRINTF(ctx, "%lld", + (long long)fnvpair_value_int64(nv)); + break; + case DATA_TYPE_UINT64: + PRINTF(ctx, "%llu", + (unsigned long long)fnvpair_value_uint64(nv)); + break; + case DATA_TYPE_HRTIME: { + hrtime_t val; + VERIFY0(nvpair_value_hrtime(nv, &val)); + PRINTF(ctx, "%llu", (unsigned long long)val); + break; + } +#if !defined(_KERNEL) && !defined(_STANDALONE) + case DATA_TYPE_DOUBLE: { + double val; + VERIFY0(nvpair_value_double(nv, &val)); + PRINTF(ctx, "%f", val); + break; + } +#endif + case DATA_TYPE_NVLIST: + ret = nvlist_to_json_impl(ctx, + fnvpair_value_nvlist(nv)); + if (ret != 0) + return (ret); + break; + case DATA_TYPE_STRING_ARRAY: { + const char **val; + uint_t valsz, i; + VERIFY0(nvpair_value_string_array(nv, &val, &valsz)); + PRINTF(ctx, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + PRINTF(ctx, ","); + PRINT_STRING(ctx, val[i]); + } + PRINTF(ctx, "]"); + break; + } + case DATA_TYPE_NVLIST_ARRAY: { + nvlist_t **val; + uint_t valsz, i; + VERIFY0(nvpair_value_nvlist_array(nv, &val, &valsz)); + PRINTF(ctx, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + PRINTF(ctx, ","); + ret = nvlist_to_json_impl(ctx, val[i]); + if (ret != 0) + return (ret); + } + PRINTF(ctx, "]"); + break; + } + case DATA_TYPE_BOOLEAN_ARRAY: { + boolean_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_boolean_array(nv, &val, &valsz)); + PRINTF(ctx, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + PRINTF(ctx, ","); + PRINTF(ctx, val[i] == B_TRUE ? + "true" : "false"); + } + PRINTF(ctx, "]"); + break; + } + case DATA_TYPE_BYTE_ARRAY: { + uchar_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_byte_array(nv, &val, &valsz)); + PRINTF(ctx, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + PRINTF(ctx, ","); + PRINTF(ctx, "%hhu", val[i]); + } + PRINTF(ctx, "]"); + break; + } + case DATA_TYPE_UINT8_ARRAY: { + uint8_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_uint8_array(nv, &val, &valsz)); + PRINTF(ctx, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + PRINTF(ctx, ","); + PRINTF(ctx, "%hhu", val[i]); + } + PRINTF(ctx, "]"); + break; + } + case DATA_TYPE_INT8_ARRAY: { + int8_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_int8_array(nv, &val, &valsz)); + PRINTF(ctx, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + PRINTF(ctx, ","); + PRINTF(ctx, "%hhd", val[i]); + } + PRINTF(ctx, "]"); + break; + } + case DATA_TYPE_UINT16_ARRAY: { + uint16_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_uint16_array(nv, &val, &valsz)); + PRINTF(ctx, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + PRINTF(ctx, ","); + PRINTF(ctx, "%hu", val[i]); + } + PRINTF(ctx, "]"); + break; + } + case DATA_TYPE_INT16_ARRAY: { + int16_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_int16_array(nv, &val, &valsz)); + PRINTF(ctx, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + PRINTF(ctx, ","); + PRINTF(ctx, "%hd", val[i]); + } + PRINTF(ctx, "]"); + break; + } + case DATA_TYPE_UINT32_ARRAY: { + uint32_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_uint32_array(nv, &val, &valsz)); + PRINTF(ctx, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + PRINTF(ctx, ","); + PRINTF(ctx, "%u", val[i]); + } + PRINTF(ctx, "]"); + break; + } + case DATA_TYPE_INT32_ARRAY: { + int32_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_int32_array(nv, &val, &valsz)); + PRINTF(ctx, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + PRINTF(ctx, ","); + PRINTF(ctx, "%d", val[i]); + } + PRINTF(ctx, "]"); + break; + } + case DATA_TYPE_UINT64_ARRAY: { + uint64_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_uint64_array(nv, &val, &valsz)); + PRINTF(ctx, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + PRINTF(ctx, ","); + PRINTF(ctx, "%llu", + (unsigned long long)val[i]); + } + PRINTF(ctx, "]"); + break; + } + case DATA_TYPE_INT64_ARRAY: { + int64_t *val; + uint_t valsz, i; + VERIFY0(nvpair_value_int64_array(nv, &val, &valsz)); + PRINTF(ctx, "["); + for (i = 0; i < valsz; i++) { + if (i > 0) + PRINTF(ctx, ","); + PRINTF(ctx, "%lld", (long long)val[i]); + } + PRINTF(ctx, "]"); + break; + } + case DATA_TYPE_UNKNOWN: + case DATA_TYPE_DONTCARE: + return (EINVAL); + } + } + + PRINTF(ctx, "}"); + + return (0); +} + +int +nvlist_to_json(nvjson_t *nvjson, nvlist_t *nvl) +{ + nvjson_context_t context = { 0 }; + context.r = nvjson; + context.p = nvjson->buf; + + if (nvjson->buf != NULL && nvjson->size < 1) + return (EINVAL); + if (nvjson->buf == NULL && nvjson->writer == NULL) + return (EINVAL); + + return (nvlist_to_json_impl(&context, nvl)); +} diff --git a/module/zcommon/json.c b/module/zcommon/json.c new file mode 100644 index 000000000000..9a50c0ed41c8 --- /dev/null +++ b/module/zcommon/json.c @@ -0,0 +1,38 @@ +// 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. + */ + +#include "json.h" + +void +json_add_output_version(nvlist_t *nvl, const char *cmd, int maj_v, int min_v) +{ + nvlist_t *ov = fnvlist_alloc(); + fnvlist_add_string(ov, "command", cmd); + fnvlist_add_uint32(ov, "vers_major", maj_v); + fnvlist_add_uint32(ov, "vers_minor", min_v); + fnvlist_add_nvlist(nvl, "output_version", ov); + fnvlist_free(ov); +} diff --git a/module/zcommon/literals.c b/module/zcommon/literals.c new file mode 100644 index 000000000000..ed87914d0609 --- /dev/null +++ b/module/zcommon/literals.c @@ -0,0 +1,78 @@ +// 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. + */ + +#include "literals.h" + +static const char *vdev_state_strings[] = { + "UNKNOWN", + "CLOSED", + "OFFLINE", + "REMOVED", + "CANT_OPEN", + "FAULTED", + "DEGRADED", + "ONLINE" +}; + +const char * +vdev_state_string(uint64_t state) +{ + if (state <= VDEV_STATE_HEALTHY) + return (vdev_state_strings[state]); + return ("UNDEFINED"); +} + +static const char *vdev_aux_strings[] = { + "NONE", + "OPEN_FAILED", + "CORRUPT_DATA", + "NO_REPLICAS", + "BAD_GUID_SUM", + "TOO_SMALL", + "BAD_LABEL", + "VERSION_NEWER", + "VERSION_OLDER", + "UNSUP_FEAT", + "SPARED", + "ERR_EXCEEDED", + "IO_FAILURE", + "BAD_LOG", + "EXTERNAL", + "SPLIT_POOL", + "BAD_ASHIFT", + "EXTERNAL_PERSIST", + "ACTIVE", + "CHILDREN_OFFLINE", + "ASHIFT_TOO_BIG" +}; + +const char * +vdev_aux_string(uint64_t aux) +{ + if (aux <= VDEV_AUX_ASHIFT_TOO_BIG) + return (vdev_aux_strings[aux]); + return ("UNDEFINED"); +} diff --git a/module/zfs/spa_stats.c b/module/zfs/spa_stats.c index 6d7cabcf766d..c579b22ce506 100644 --- a/module/zfs/spa_stats.c +++ b/module/zfs/spa_stats.c @@ -25,6 +25,7 @@ #include #include #include +#include /* * Keeps stats on last N reads per spa_t, disabled by default. @@ -1037,6 +1038,53 @@ spa_iostats_destroy(spa_t *spa) mutex_destroy(&shk->lock); } +static void * +spa_stats_json_addr(kstat_t *ksp, loff_t n) +{ + if (n == 0) + return (ksp->ks_private); + return (NULL); +} + +static int +spa_stats_json_data(char *buf, size_t size, void *data) +{ + spa_t *spa = (spa_t *)data; + + return (spa_stats_json_generate(spa, buf, size)); +} + +static void +spa_stats_json_init(spa_t *spa) +{ + char *name; + kstat_t *ksp; + + mutex_init(&spa->spa_stats_json.ssj_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_NO_HEADERS); + spa->spa_stats_json.ssj_kstat = ksp; + if (ksp) { + ksp->ks_lock = &spa->spa_stats_json.ssj_lock; + ksp->ks_data = NULL; + ksp->ks_private = spa; + kstat_set_raw_ops(ksp, NULL, spa_stats_json_data, + spa_stats_json_addr); + kstat_install(ksp); + } + + kmem_strfree(name); +} + +static void +spa_stats_json_destroy(spa_t *spa) +{ + if (spa->spa_stats_json.ssj_kstat) + kstat_delete(spa->spa_stats_json.ssj_kstat); + mutex_destroy(&spa->spa_stats_json.ssj_lock); +} + void spa_stats_init(spa_t *spa) { @@ -1047,11 +1095,13 @@ spa_stats_init(spa_t *spa) spa_state_init(spa); spa_guid_init(spa); spa_iostats_init(spa); + spa_stats_json_init(spa); } void spa_stats_destroy(spa_t *spa) { + spa_stats_json_destroy(spa); spa_iostats_destroy(spa); spa_health_destroy(spa); spa_tx_assign_destroy(spa); diff --git a/module/zfs/spa_stats_json.c b/module/zfs/spa_stats_json.c new file mode 100644 index 000000000000..e668858cafd8 --- /dev/null +++ b/module/zfs/spa_stats_json.c @@ -0,0 +1,410 @@ +// 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 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) 2025, Klara, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "json.h" +#include "literals.h" + +#define GUID_STRBUF_LEN 32 +#define BUF_LEN 256 + +static uint_t zfs_lockless_read_enabled = 0; + +static char * +vdev_name(vdev_t *vd) +{ + char *name = kmem_alloc(BUF_LEN, KM_SLEEP); + + if (strcmp(vd->vdev_ops->vdev_op_type, "root") == 0) { + strlcpy(name, spa_name(vd->vdev_spa), BUF_LEN); + return (name); + } + + if (vd->vdev_not_present) { + snprintf(name, BUF_LEN, "%llu", (u_longlong_t)vd->vdev_guid); + return (name); + } + + if (vd->vdev_path != NULL) { + /* No path or partition stripping. */ + strlcpy(name, vd->vdev_path, BUF_LEN); + return (name); + } + + if (strcmp(vd->vdev_ops->vdev_op_type, VDEV_TYPE_RAIDZ) == 0) { + snprintf(name, BUF_LEN, "%s%llu", + vd->vdev_ops->vdev_op_type, + (u_longlong_t)vdev_get_nparity(vd)); + } else if (strcmp(vd->vdev_ops->vdev_op_type, VDEV_TYPE_DRAID) == 0) { + vdev_draid_config_t *vdc = vd->vdev_tsd; + snprintf(name, BUF_LEN, "%s%llu:%llud:%lluc:%llus", + VDEV_TYPE_DRAID, + (u_longlong_t)vdc->vdc_nparity, + (u_longlong_t)vdc->vdc_ndata, + (u_longlong_t)vd->vdev_children, + (u_longlong_t)vdc->vdc_nspares); + } else { + strlcpy(name, vd->vdev_ops->vdev_op_type, BUF_LEN); + } + + const size_t namelen = strlen(name); + if (namelen < BUF_LEN - 1) + snprintf(name + namelen, BUF_LEN - namelen, "-%llu", + (u_longlong_t)vd->vdev_id); + + return (name); +} + +static void +add_vdev(nvlist_t *parent, vdev_t *vd) +{ + /* Numbers are provided as literals composed into strings */ + + if (strcmp(vd->vdev_ops->vdev_op_type, VDEV_TYPE_INDIRECT) == 0) + return; + + nvlist_t *nvl = fnvlist_alloc(); + char *buf = kmem_alloc(BUF_LEN, KM_SLEEP); + + vdev_stat_t *vs = kmem_alloc(sizeof (*vs), KM_SLEEP); + vdev_get_stats_ex(vd, vs, NULL); + + char *vname = vdev_name(vd); + fnvlist_add_string(nvl, "name", vname); + + fnvlist_add_string(nvl, "vdev_type", vd->vdev_ops->vdev_op_type); + + if (vd->vdev_guid != 0) { + snprintf(buf, BUF_LEN, "%llu", (u_longlong_t)vd->vdev_guid); + fnvlist_add_string(nvl, "guid", buf); + } + + if (vd->vdev_path != NULL) + fnvlist_add_string(nvl, "path", vd->vdev_path); + if (vd->vdev_physpath != NULL) + fnvlist_add_string(nvl, "phys_path", vd->vdev_physpath); + if (vd->vdev_devid != NULL) + fnvlist_add_string(nvl, "devid", vd->vdev_devid); + + if (vd->vdev_ishole) + fnvlist_add_string(nvl, "class", VDEV_TYPE_HOLE); + else if (vd->vdev_isl2cache) + fnvlist_add_string(nvl, "class", VDEV_TYPE_L2CACHE); + else if (vd->vdev_isspare) + fnvlist_add_string(nvl, "class", VDEV_TYPE_SPARE); + else if (vd->vdev_islog) + fnvlist_add_string(nvl, "class", VDEV_TYPE_LOG); + else { + vdev_alloc_bias_t bias = vd->vdev_alloc_bias; + if (bias == VDEV_BIAS_NONE && vd->vdev_parent != NULL) + bias = vd->vdev_parent->vdev_alloc_bias; + switch (bias) { + case VDEV_BIAS_LOG: + fnvlist_add_string(nvl, "class", + VDEV_ALLOC_BIAS_LOG); + break; + case VDEV_BIAS_SPECIAL: + fnvlist_add_string(nvl, "class", + VDEV_ALLOC_BIAS_SPECIAL); + break; + case VDEV_BIAS_DEDUP: + fnvlist_add_string(nvl, "class", + VDEV_ALLOC_BIAS_DEDUP); + break; + case VDEV_BIAS_NONE: + default: + fnvlist_add_string(nvl, "class", "normal"); + } + } + + fnvlist_add_string(nvl, "state", vdev_state_string(vd->vdev_state)); + + if (vd->vdev_isspare) { + if (vd->vdev_stat.vs_aux == VDEV_AUX_SPARED) { + fnvlist_add_string(nvl, "state", "INUSE"); + /* used_by is not provided */ + } else if (vd->vdev_state == VDEV_STATE_HEALTHY) { + fnvlist_add_string(nvl, "state", "AVAIL"); + } + } else { + if (vs->vs_alloc) { + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_alloc); + fnvlist_add_string(nvl, "alloc_space", buf); + } + if (vs->vs_space) { + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_space); + fnvlist_add_string(nvl, "total_space", buf); + } + if (vs->vs_dspace) { + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_dspace); + fnvlist_add_string(nvl, "def_space", buf); + } + if (vs->vs_rsize) { + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_rsize); + fnvlist_add_string(nvl, "rep_dev_size", buf); + } + if (vs->vs_esize) { + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_esize); + fnvlist_add_string(nvl, "ex_dev_size", buf); + } + if (vs->vs_self_healed) { + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_self_healed); + fnvlist_add_string(nvl, "self_healed", buf); + } + if (vs->vs_pspace) { + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_pspace); + fnvlist_add_string(nvl, "phys_space", buf); + } + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_read_errors); + fnvlist_add_string(nvl, "read_errors", buf); + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_write_errors); + fnvlist_add_string(nvl, "write_errors", buf); + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_checksum_errors); + fnvlist_add_string(nvl, "checksum_errors", buf); + if (vs->vs_scan_processed) { + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_scan_processed); + fnvlist_add_string(nvl, "scan_processed", buf); + } + if (vs->vs_checkpoint_space) { + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_checkpoint_space); + fnvlist_add_string(nvl, "checkpoint_space", buf); + } + if (vs->vs_resilver_deferred) { + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_resilver_deferred); + fnvlist_add_string(nvl, "resilver_deferred", buf); + } + if (vd->vdev_children == 0) { + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_slow_ios); + fnvlist_add_string(nvl, "slow_ios", buf); + } + } + + if (vd->vdev_not_present) { + fnvlist_add_string(nvl, "not_present", "1"); + if (vd->vdev_path != NULL) + fnvlist_add_string(nvl, "was", vd->vdev_path); + } else if (vs->vs_aux != VDEV_AUX_NONE) { + fnvlist_add_string(nvl, "aux", vdev_aux_string(vs->vs_aux)); + } else if (vd->vdev_children == 0 && !vd->vdev_isspare && + vs->vs_configured_ashift < vs->vs_physical_ashift) { + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_configured_ashift); + fnvlist_add_string(nvl, "configured_ashift", buf); + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_physical_ashift); + fnvlist_add_string(nvl, "physical_ashift", buf); + } + + if (vs->vs_scan_removing != 0) { + snprintf(buf, BUF_LEN, "%llu", + (u_longlong_t)vs->vs_scan_removing); + fnvlist_add_string(nvl, "removing", buf); + } else if (vs->vs_noalloc != 0) { + snprintf(buf, BUF_LEN, "%llu", (u_longlong_t)vs->vs_noalloc); + fnvlist_add_string(nvl, "noalloc", buf); + } + + /* The vdev init & trim info is not provided */ + + const uint64_t n = vd->vdev_children; + const boolean_t is_root = + (strcmp(vd->vdev_ops->vdev_op_type, "root") == 0) ? + B_TRUE : B_FALSE; + if (n > 0) { + nvlist_t *vdevs = fnvlist_alloc(); + for (uint64_t i = 0; i < n; i++) { + vdev_t *ch = vd->vdev_child[i]; + if (ch == NULL) + continue; + /* logs/dedup/special are provided separately */ + if (is_root == B_TRUE && ch->vdev_alloc_bias + != VDEV_BIAS_NONE) + continue; + add_vdev(vdevs, vd->vdev_child[i]); + } + fnvlist_add_nvlist(nvl, "vdevs", vdevs); + nvlist_free(vdevs); + } + + fnvlist_add_nvlist(parent, vname, nvl); + nvlist_free(nvl); + kmem_free(vname, BUF_LEN); + kmem_free(vs, sizeof (*vs)); + kmem_free(buf, BUF_LEN); +} + +int +spa_stats_json_generate(spa_t *spa, char *buf, size_t size) +{ + int error = 0; + nvlist_t *nvroot, *pools, *pool, *vdevs; + vdev_t *rootvd; + char *str; + int locked = 0; + int n = 0; + + str = kmem_alloc(GUID_STRBUF_LEN, KM_SLEEP); + + while (locked == 0 && n++ < 10) + locked = spa_config_tryenter(spa, SCL_CONFIG, FTAG, RW_READER); + if (locked == 0 && zfs_lockless_read_enabled == 0) + return (EAGAIN); + + pool = fnvlist_alloc(); + fnvlist_add_string(pool, "name", spa_name(spa)); + fnvlist_add_string(pool, "state", spa_state_to_name(spa)); + + snprintf(str, GUID_STRBUF_LEN, "%llu", (u_longlong_t)spa_guid(spa)); + fnvlist_add_string(pool, ZPOOL_CONFIG_POOL_GUID, str); + snprintf(str, GUID_STRBUF_LEN, "%llu", + (u_longlong_t)(spa->spa_config_txg)); + fnvlist_add_string(pool, ZPOOL_CONFIG_POOL_TXG, str); + + fnvlist_add_string(pool, "spa_version", SPA_VERSION_STRING); + fnvlist_add_string(pool, "zpl_version", ZPL_VERSION_STRING); + + /* The status, action, msgid, moreinfo are not provided */ + + /* root vdev */ + rootvd = spa->spa_root_vdev; + if (rootvd != NULL) { + vdevs = fnvlist_alloc(); + add_vdev(vdevs, rootvd); + fnvlist_add_nvlist(pool, "vdevs", vdevs); + nvlist_free(vdevs); + } + + /* dedup */ + n = 0; + vdevs = fnvlist_alloc(); + for (size_t i = 0; rootvd != NULL && i < rootvd->vdev_children; i++) { + vdev_t *vd = rootvd->vdev_child[i]; + if (vd->vdev_alloc_bias != VDEV_BIAS_DEDUP) + continue; + n++; + add_vdev(vdevs, vd); + } + if (n > 0) + fnvlist_add_nvlist(pool, "dedup", vdevs); + nvlist_free(vdevs); + + /* special */ + n = 0; + vdevs = fnvlist_alloc(); + for (size_t i = 0; rootvd != NULL && i < rootvd->vdev_children; i++) { + vdev_t *vd = rootvd->vdev_child[i]; + if (vd->vdev_alloc_bias != VDEV_BIAS_SPECIAL) + continue; + n++; + add_vdev(vdevs, vd); + } + if (n > 0) + fnvlist_add_nvlist(pool, "special", vdevs); + nvlist_free(vdevs); + + /* logs */ + n = 0; + vdevs = fnvlist_alloc(); + for (size_t i = 0; rootvd != NULL && i < rootvd->vdev_children; i++) { + vdev_t *vd = rootvd->vdev_child[i]; + if (vd->vdev_alloc_bias != VDEV_BIAS_LOG) + continue; + n++; + add_vdev(vdevs, vd); + } + if (n > 0) + fnvlist_add_nvlist(pool, "logs", vdevs); + nvlist_free(vdevs); + + /* l2cache */ + if (spa->spa_l2cache.sav_count > 0) { + vdevs = fnvlist_alloc(); + for (int i = 0; i < spa->spa_l2cache.sav_count; i++) + add_vdev(vdevs, spa->spa_l2cache.sav_vdevs[i]); + fnvlist_add_nvlist(pool, "l2cache", vdevs); + nvlist_free(vdevs); + } + + /* spares */ + if (spa->spa_spares.sav_count > 0) { + vdevs = fnvlist_alloc(); + for (int i = 0; i < spa->spa_spares.sav_count; i++) + add_vdev(vdevs, spa->spa_spares.sav_vdevs[i]); + fnvlist_add_nvlist(pool, "spares", vdevs); + nvlist_free(vdevs); + } + + snprintf(str, GUID_STRBUF_LEN, "%llu", + (u_longlong_t)spa_approx_errlog_size(spa)); + fnvlist_add_string(pool, ZPOOL_CONFIG_ERRCOUNT, str); + + nvroot = fnvlist_alloc(); + json_add_output_version(nvroot, "kstat zpool status", 0, 1); + pools = fnvlist_alloc(); + fnvlist_add_nvlist(pools, spa_name(spa), pool); + nvlist_free(pool); + fnvlist_add_nvlist(nvroot, "pools", pools); + nvlist_free(pools); + + if (locked != 0) + spa_config_exit(spa, SCL_CONFIG, FTAG); + + nvjson_t nvjson = { + .buf = buf, + .size = size, + }; + error = nvlist_to_json(&nvjson, nvroot); + nvlist_free(nvroot); + + kmem_free(str, GUID_STRBUF_LEN); + + return (error); +} + +ZFS_MODULE_PARAM(zfs, zfs_, lockless_read_enabled, UINT, ZMOD_RW, + "Enables lockless traversal of kernel structures in emergencies"); diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 9ea511dec0eb..58ca15e18ece 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -767,6 +767,12 @@ tests = ['inuse_004_pos', 'inuse_005_pos', 'inuse_008_pos', 'inuse_009_pos'] post = tags = ['functional', 'inuse'] +[tests/functional/kstat] +pre = +post = +tests = ['pool_status_json'] +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/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index acff5e57db93..7c52105fa930 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -1602,6 +1602,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/io/psync.ksh \ functional/io/setup.ksh \ functional/io/sync.ksh \ + functional/kstat/pool_status_json.ksh \ functional/l2arc/cleanup.ksh \ functional/l2arc/l2arc_arcstats_pos.ksh \ functional/l2arc/l2arc_l2miss_pos.ksh \ diff --git a/tests/zfs-tests/tests/functional/kstat/pool_status_json.ksh b/tests/zfs-tests/tests/functional/kstat/pool_status_json.ksh new file mode 100755 index 000000000000..60e43f70a842 --- /dev/null +++ b/tests/zfs-tests/tests/functional/kstat/pool_status_json.ksh @@ -0,0 +1,258 @@ +#!/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 + +# +# DESCRIPTION: +# Verify pool's status_json kstat output. +# +# STRATEGY: +# 1. Compare kstat output with zpool status -jp output. +# + +log_assert "Verify pool's status_json kstat output." + +mkdir -p $TESTDIR +truncate -s 80M $TESTDIR/file{1..28} +DISK=${DISKS%% *} + +# Create complex pool configs to exercise JSON +zpool create -f tp1 draid $TESTDIR/file{1..10} \ + special $DISK \ + dedup $TESTDIR/file11 \ + special $TESTDIR/file12 \ + cache $TESTDIR/file13 \ + log $TESTDIR/file14 +zpool create -f tp2 mirror $TESTDIR/file{15,16} \ + raidz1 $TESTDIR/file{17,18,19} \ + cache $TESTDIR/file20 \ + log $TESTDIR/file21 \ + special mirror $TESTDIR/file{22,23} \ + dedup mirror $TESTDIR/file{24,25} \ + spare $TESTDIR/file{26,27,28} + +function cleanup +{ + zpool destroy tp1 + zpool destroy tp2 + + rm $TESTDIR/file{1..28} + rmdir $TESTDIR +} + +log_onexit cleanup + +function sanity_check +{ + log_must eval 'kstat_pool tp1 status_json | jq >/dev/null' + log_must eval 'kstat_pool tp2 status_json | jq >/dev/null' +} + +TESTPOOL=tp0 + +# JSON from userland using zpool status +function ujq +{ + zpool status -jp $TESTPOOL | jq "$*" +} +function ujqe +{ + zpool status -jp $TESTPOOL | jq -e "$*" >/dev/null +} + +# JSON from kernel using kstat +function kjq +{ + kstat_pool $TESTPOOL status_json | jq "$*" +} +function kjqe +{ + kstat_pool $TESTPOOL status_json | jq -e "$*" >/dev/null +} + +function verify_testpool1 +{ + TESTPOOL=tp1 + + log_must ujqe '.pools | has("tp1")' + log_must kjqe '.pools | has("tp1")' + + json=".pools.tp1 | {name, state, pool_guid, spa_version, zpl_version, error_count}" + log_must diff -u <(ujq "$json") <(kjq "$json") + log_must kjqe '.pools.tp1 | has("txg")' + + log_must ujqe '.pools.tp1 | has("vdevs")' + log_must kjqe '.pools.tp1 | has("vdevs")' + + # root vdev { + log_must ujqe '.pools.tp1.vdevs | has("tp1")' + log_must kjqe '.pools.tp1.vdevs | has("tp1")' + + json=".pools.tp1.vdevs.tp1 | {name, vdev_type, guid, class, state, total_space, def_space, read_erros, write_errors, checksum_errors}" + log_must diff -u <(ujq "$json") <(kjq "$json") + log_must kjqe '.pools.tp1.vdevs.tp1 | has("alloc_space")' + # } + + # draid vdev { + log_must ujqe '.pools.tp1.vdevs.tp1 | has("vdevs")' + log_must kjqe '.pools.tp1.vdevs.tp1 | has("vdevs")' + + log_must ujqe '.pools.tp1.vdevs.tp1.vdevs | has("draid1:8d:10c:0s-0")' + log_must kjqe '.pools.tp1.vdevs.tp1.vdevs | has("draid1:8d:10c:0s-0")' + + json='.pools.tp1.vdevs.tp1.vdevs["draid1:8d:10c:0s-0"] | {name, total_space, def_space, rep_dev_size, read_errors, write_errors, checksum_errors}' + log_must diff -u <(ujq "$json") <(kjq "$json") + log_must diff -u <(echo \"draid\") <(kjq '.pools.tp1.vdevs.tp1.vdevs["draid1:8d:10c:0s-0"].vdev_type') + log_must diff -u <(echo \"normal\") <(kjq '.pools.tp1.vdevs.tp1.vdevs["draid1:8d:10c:0s-0"].class') + log_must diff -u <(echo \"ONLINE\") <(kjq '.pools.tp1.vdevs.tp1.vdevs["draid1:8d:10c:0s-0"].state') + log_must kjqe '.pools.tp1.vdevs.tp1.vdevs["draid1:8d:10c:0s-0"] | has("guid")' + + json='.pools.tp1.vdevs.tp1.vdevs["draid1:8d:10c:0s-0"].vdevs' + log_must diff -u <(ujq "$json") <(kjq "$json") + # } + + # dedup { + log_must ujqe '.pools.tp1 | has("dedup")' + log_must kjqe '.pools.tp1 | has("dedup")' + + json='.pools.tp1.dedup' + log_must diff -u <(ujq "$json") <(kjq "$json") + # } + + # special { + log_must ujqe '.pools.tp1 | has("special")' + log_must kjqe '.pools.tp1 | has("special")' + + ujson='.pools.tp1.special.loop5 | del(.name)' + kjson='.pools.tp1.special["/dev/loop5"] | del(.name)' + log_must diff -u <(ujq "$ujson") <(kjq "$kjson") + + json='.pools.tp1.special["/var/tmp/testdir/file12"]' + log_must diff -u <(ujq "$json") <(kjq "$json") + # } + + # logs { + log_must ujqe '.pools.tp1 | has("logs")' + log_must kjqe '.pools.tp1 | has("logs")' + + json='.pools.tp1.logs' + log_must diff -u <(ujq "$json") <(kjq "$json") + # } + + # l2cache { + log_must ujqe '.pools.tp1 | has("l2cache")' + log_must kjqe '.pools.tp1 | has("l2cache")' + + json='.pools.tp1.l2cache' + log_must diff -u <(ujq "$json") <(kjq "$json") + # } +} + +function verify_testpool2 +{ + TESTPOOL=tp2 + + log_must ujqe '.pools | has("tp2")' + log_must kjqe '.pools | has("tp2")' + + json=".pools.tp2 | {name, state, pool_guid, spa_version, zpl_version, error_count}" + log_must diff -u <(ujq "$json") <(kjq "$json") + log_must kjqe '.pools.tp2 | has("txg")' + + log_must ujqe '.pools.tp2 | has("vdevs")' + log_must kjqe '.pools.tp2 | has("vdevs")' + + # root vdev { + log_must ujqe '.pools.tp2.vdevs | has("tp2")' + log_must kjqe '.pools.tp2.vdevs | has("tp2")' + + json=".pools.tp2.vdevs.tp2 | {name, vdev_type, guid, class, state, total_space, def_space, read_erros, write_errors, checksum_errors}" + log_must diff -u <(ujq "$json") <(kjq "$json") + log_must kjqe '.pools.tp2.vdevs.tp2 | has("alloc_space")' + # } + + # mirror-0 vdev { + log_must ujqe '.pools.tp2.vdevs.tp2 | has("vdevs")' + log_must kjqe '.pools.tp2.vdevs.tp2 | has("vdevs")' + + log_must ujqe '.pools.tp2.vdevs.tp2.vdevs | has("mirror-0")' + log_must kjqe '.pools.tp2.vdevs.tp2.vdevs | has("mirror-0")' + + json='.pools.tp2.vdevs.tp2.vdevs["mirror-0"] | {name, vdev_type, class, state, guid, total_space, def_space, rep_dev_size, read_errors, write_errors, checksum_errors}' + log_must diff -u <(ujq "$json") <(kjq "$json") + + json='.pools.tp2.vdevs.tp2.vdevs["mirror-0"].vdevs' + log_must diff -u <(ujq "$json") <(kjq "$json") + # } + + # dedup { + log_must ujqe '.pools.tp2 | has("dedup")' + log_must kjqe '.pools.tp2 | has("dedup")' + + json='.pools.tp2.dedup' + log_must diff -u <(ujq "$json") <(kjq "$json") + # } + + # special { + log_must ujqe '.pools.tp2 | has("special")' + log_must kjqe '.pools.tp2 | has("special")' + + json='.pools.tp2.special' + log_must diff -u <(ujq "$json") <(kjq "$json") + # } + + # logs { + log_must ujqe '.pools.tp2 | has("logs")' + log_must kjqe '.pools.tp2 | has("logs")' + + json='.pools.tp2.logs' + log_must diff -u <(ujq "$json") <(kjq "$json") + # } + + # l2cache { + log_must ujqe '.pools.tp2 | has("l2cache")' + log_must kjqe '.pools.tp2 | has("l2cache")' + + json='.pools.tp2.l2cache' + log_must diff -u <(ujq "$json") <(kjq "$json") + # } + + # spares { + log_must ujqe '.pools.tp2 | has("spares")' + log_must kjqe '.pools.tp2 | has("spares")' + + json='.pools.tp2.spares' + log_must diff -u <(ujq "$json") <(kjq "$json") + # } +} + +log_must sanity_check +log_must verify_testpool1 +log_must verify_testpool2 + +log_pass "Pool's status_json kstat provides expected output."