From 420a125bdccf0662785bb90921fb45e2c2f96434 Mon Sep 17 00:00:00 2001 From: fredw Date: Mon, 25 Mar 2024 14:25:38 -0600 Subject: [PATCH 1/2] Add a diagnostic kstat for obtaining pool status This kstat output does not require taking the spa_namespace lock, as in the case for 'zpool status'. It can be used for investigations when pools are in a hung state while holding global locks required for a traditional 'zpool status' to proceed. This kstat is not safe to use in conditions where pools are in the process of configuration changes (i.e., adding/removing devices). Therefore, this kstat is not intended to be a general replacement or alternative to using 'zpool status'. Sponsored-by: Wasabi Technology, Inc. Sponsored-By: Klara Inc. Co-authored-by: Don Brady Signed-off-by: Don Brady --- include/Makefile.am | 2 + include/os/freebsd/spl/sys/kstat.h | 4 +- include/os/linux/spl/sys/kstat.h | 4 +- include/sys/jprint.h | 70 ++ include/sys/spa.h | 3 + include/sys/spa_impl.h | 2 + include/sys/spa_json_stats.h | 40 ++ lib/libspl/include/sys/kstat.h | 50 +- lib/libzpool/Makefile.am | 2 + module/Kbuild.in | 2 + module/Makefile.bsd | 2 + module/os/linux/spl/spl-kstat.c | 2 +- module/zfs/dbuf_stats.c | 2 +- module/zfs/jprint.c | 423 ++++++++++++ module/zfs/spa.c | 6 +- module/zfs/spa_json_stats.c | 649 ++++++++++++++++++ module/zfs/spa_stats.c | 49 ++ tests/runfiles/common.run | 3 +- tests/zfs-tests/tests/Makefile.am | 1 + .../zpool_status/zpool_status_kstat_pos.ksh | 66 ++ 20 files changed, 1329 insertions(+), 53 deletions(-) create mode 100644 include/sys/jprint.h create mode 100644 include/sys/spa_json_stats.h create mode 100644 module/zfs/jprint.c create mode 100644 module/zfs/spa_json_stats.c create mode 100755 tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_kstat_pos.ksh diff --git a/include/Makefile.am b/include/Makefile.am index fa725c2e7a..fcd0e51c72 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -175,6 +175,8 @@ COMMON_H = \ KERNEL_H = \ + sys/jprint.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/jprint.h b/include/sys/jprint.h new file mode 100644 index 0000000000..fef1a79642 --- /dev/null +++ b/include/sys/jprint.h @@ -0,0 +1,70 @@ +/* + * 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_JPRINT_H +#define _SYS_JPRINT_H + +/* maximum stack nesting */ +#define JP_MAX_STACK 32 + +enum jp_type { + JP_OBJECT = 1, + JP_ARRAY +}; + +struct jp_stack { + enum jp_type type; + int nelem; +}; + +typedef struct jprint { + char *buffer; /* pointer to application's buffer */ + size_t buflen; /* length of buffer */ + char *bufp; /* current write position in buffer */ + char tmpbuf[32]; /* local buffer for conversions */ + int error; /* error code */ + int ncall; /* API call number on which error occurred */ + struct jp_stack /* stack of array/object nodes */ + stack[JP_MAX_STACK]; + int stackp; +} jprint_t; + +/* error return codes */ +#define JPRINT_OK 0 /* no error */ +#define JPRINT_BUF_FULL 1 /* output buffer full */ +#define JPRINT_NEST_ERROR 2 /* nesting error */ +#define JPRINT_STACK_FULL 3 /* array/object nesting */ +#define JPRINT_STACK_EMPTY 4 /* stack underflow error */ +#define JPRINT_OPEN 5 /* not all objects closed */ +#define JPRINT_FMT 6 /* format error */ + +const char *jp_errorstring(int err); +int jp_error(jprint_t *jp); +void jp_open(jprint_t *jp, char *buffer, size_t buflen); +int jp_close(jprint_t *jp); +int jp_errorpos(jprint_t *jp); +int jp_printf(jprint_t *jp, const char *fmt, ...); + +#endif /* _SYS_JPRINT_H */ diff --git a/include/sys/spa.h b/include/sys/spa.h index 93f381affd..3bc7518569 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 4fc6f22fcb..2fb9c53832 100644 --- a/include/sys/spa_impl.h +++ b/include/sys/spa_impl.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -441,6 +442,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/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 81949bf9e5..b114fb3303 100644 --- a/lib/libzpool/Makefile.am +++ b/lib/libzpool/Makefile.am @@ -112,6 +112,7 @@ nodist_libzpool_la_SOURCES = \ module/zfs/fm.c \ module/zfs/gzip.c \ module/zfs/hkdf.c \ + module/zfs/jprint.c \ module/zfs/lz4.c \ module/zfs/lz4_zfs.c \ module/zfs/lzjb.c \ @@ -131,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 a119198dbf..7d7acfb463 100644 --- a/module/Kbuild.in +++ b/module/Kbuild.in @@ -353,6 +353,7 @@ ZFS_OBJS := \ fm.o \ gzip.o \ hkdf.o \ + jprint.o \ lz4.o \ lz4_zfs.o \ lzjb.o \ @@ -372,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 534f325713..971ada5a58 100644 --- a/module/Makefile.bsd +++ b/module/Makefile.bsd @@ -282,6 +282,7 @@ SRCS+= abd.c \ edonr_zfs.c \ fm.c \ gzip.c \ + jprint.c \ lz4.c \ lz4_zfs.c \ lzjb.c \ @@ -303,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/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/jprint.c b/module/zfs/jprint.c new file mode 100644 index 0000000000..7070423aed --- /dev/null +++ b/module/zfs/jprint.c @@ -0,0 +1,423 @@ +/* + * 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 + +/* Formats for int64_t and uint64_t */ +#ifndef PRId64 +#define PRId64 "lld" /* %D, int64_t */ +#define PRIu64 "llu" /* %U, uint64_t */ +#endif + +/* literal key length maximum */ +#define KEYLEN 255 + +/* return error position (call number of jp_printf) */ +int +jp_errorpos(jprint_t *jp) +{ + return (jp->ncall); +} + +/* return string for error code */ +const char * +jp_errorstring(int err) +{ + switch (err) { + case JPRINT_OK: return ("jprint ok"); + case JPRINT_BUF_FULL: return ("jprint buffer full"); + case JPRINT_NEST_ERROR: return ("jprint nest error"); + case JPRINT_STACK_FULL: return ("jprint stack full"); + case JPRINT_STACK_EMPTY: return ("jprint stack empty"); + case JPRINT_OPEN: return ("jprint open"); + case JPRINT_FMT: return ("jprint format"); + default: return ("jprint unknown error"); + } + return ("jprint unknown error"); +} + +/* return error from jprint_t */ +int +jp_error(jprint_t *jp) +{ + return (jp->error); +} + +/* open json using buffer of length buflen */ +void +jp_open(jprint_t *jp, char *buffer, size_t buflen) +{ + jp->buffer = jp->bufp = buffer; + jp->buflen = buflen; + jp->error = JPRINT_OK; + jp->ncall = 0; + jp->stackp = -1; + *buffer = '\0'; +} + + +/* close json (return out of memory error) */ +int +jp_close(jprint_t *jp) +{ + if (jp->error != JPRINT_OK) + return (jp->error); + if (jp->stackp != -1) + jp->error = JPRINT_OPEN; + return (jp->error); +} + + +/* put character to json */ +static void +jp_putc(jprint_t *jp, char c) +{ + if (jp->error == JPRINT_OK) { + if ((jp->bufp - jp->buffer + 1) >= jp->buflen) + jp->error = JPRINT_BUF_FULL; + else { + *jp->bufp++ = c; + *jp->bufp = '\0'; + } + } +} + + +/* put string to json */ +static void +jp_puts(jprint_t *jp, char *s) +{ + while (*s && (jp->error == JPRINT_OK)) + jp_putc(jp, *s++); +} + + +/* put quoted string to json */ +static void +jp_putsq(jprint_t *jp, char *s) +{ + static const char *hex = "0123456789ABCDEF"; + int c; + + if (s == NULL) { + jp_puts(jp, (char *)"null"); + return; + } + jp_putc(jp, '\"'); + while (*s && (jp->error == JPRINT_OK)) { + c = (int)*s++; + /* formfeed, newline, return, tab, backspace */ + if (c == 12) + jp_puts(jp, (char *)"\\f"); + else if (c == 10) + jp_puts(jp, (char *)"\\n"); + else if (c == 13) + jp_puts(jp, (char *)"\\r"); + else if (c == 9) + jp_puts(jp, (char *)"\\t"); + else if (c == 8) + jp_puts(jp, (char *)"\\b"); + /* + * all characters from 0x00 to 0x1f, and 0x7f are + * escaped as: \u00xx + */ + else if (((0 <= c) && (c <= 0x1f)) || (c == 0x7f)) { + jp_puts(jp, (char *)"\\u00"); + jp_putc(jp, hex[(c >> 4) & 0x0f]); + jp_putc(jp, hex[c & 0x0f]); + /* * " \ / */ + } else if (c == '"') + jp_puts(jp, (char *)"\\\""); + else if (c == '\\') + jp_puts(jp, (char *)"\\\\"); + else if (c == '/') + jp_puts(jp, (char *)"\\/"); + /* + * 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 + jp_putc(jp, (char)c); + } + jp_putc(jp, '\"'); +} + + +/* put out key if object open. error if nothing open */ +static int +jp_key(jprint_t *jp, char *key) +{ + if (jp->error != JPRINT_OK) + goto err; + /* at top level, no frame exists yet, no error */ + if (jp->stackp == -1) + goto err; + /* stackp has been "popped" too many times */ + if (jp->stackp < -1) { + jp->error = JPRINT_STACK_EMPTY; + goto err; + } + /* put comma separator in (both object and array) */ + if (++jp->stack[jp->stackp].nelem > 1) + jp_putc(jp, ','); + /* if its in an object, put out the key and separator */ + if (jp->stack[jp->stackp].type == JP_OBJECT) { + jp_putsq(jp, key); + jp_putc(jp, ':'); + } +err: + return (jp->error); +} + + +/* printf to json */ +int +jp_printf(jprint_t *jp, const char *fmt, ...) +{ + char key[KEYLEN + 1]; + int k, i; + va_list ap; + int n; + unsigned int u; + int64_t n64; + uint64_t u64; + boolean_t b; + char *s; + char *start = jp->bufp; + + if (jp->error != JPRINT_OK) + return (-1); + ++jp->ncall; + va_start(ap, fmt); + key[k = 0] = '\0'; + while (*fmt && (jp->error == JPRINT_OK)) { + /* + * If we need to debug jprint format, + * zfs_dbgmsg("====> jprint char = %c\n", *fmt); + */ + switch (*fmt) { + case '%': + ++fmt; + switch (*fmt) { + case 'k': /* next parameter is key */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + s = va_arg(ap, char *); + if (strlen(s) <= KEYLEN) + strcpy(key, s); + else + jp->error = JPRINT_FMT; + break; + case 'd': /* next parameter is int */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + n = va_arg(ap, int); + i = snprintf( + jp->tmpbuf, sizeof (jp->tmpbuf), + "%d", n); + if (jp_key(jp, key) == JPRINT_OK) { + if ((i >= sizeof (jp->tmpbuf)) || + (i < 0)) + jp_puts(jp, (char *)"####"); + else + jp_puts(jp, jp->tmpbuf); + } + key[k = 0] = '\0'; + break; + case 'u': /* next parameter is unsigned int */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + u = va_arg(ap, unsigned int); + i = snprintf( + jp->tmpbuf, sizeof (jp->tmpbuf), + "%u", u); + if (jp_key(jp, key) == JPRINT_OK) { + if ((i >= sizeof (jp->tmpbuf)) || + (i < 0)) + jp_puts(jp, (char *)"####"); + else + jp_puts(jp, jp->tmpbuf); + } + key[k = 0] = '\0'; + break; + case 'U': /* next parameter is uint64_t */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + u64 = va_arg(ap, uint64_t); + i = snprintf( + jp->tmpbuf, sizeof (jp->tmpbuf), + "%" PRIu64, u64); + if (jp_key(jp, key) == JPRINT_OK) { + if ((i >= sizeof (jp->tmpbuf)) || + (i < 0)) + jp_puts(jp, (char *)"####"); + else + jp_puts(jp, jp->tmpbuf); + } + key[k = 0] = '\0'; + break; + case 'D': /* next parameter is int64_t */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + n64 = va_arg(ap, int64_t); + i = snprintf( + jp->tmpbuf, sizeof (jp->tmpbuf), + "%" PRId64, n64); + if (jp_key(jp, key) == JPRINT_OK) { + if ((i >= sizeof (jp->tmpbuf)) || + (i < 0)) + jp_puts(jp, (char *)"####"); + else + jp_puts(jp, jp->tmpbuf); + } + key[k = 0] = '\0'; + break; + case 's': /* next parameter is string */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + s = va_arg(ap, char *); + if (jp_key(jp, key) == JPRINT_OK) + jp_putsq(jp, s); + key[k = 0] = '\0'; + break; + case 'b': /* next parameter is boolean */ + if (jp->stackp < 0) { + jp->error = JPRINT_STACK_EMPTY; + break; + } + if (jp_key(jp, key) == JPRINT_OK) { + b = (boolean_t)va_arg(ap, int); + s = b ? + (char *)"true" : + (char *)"false"; + jp_puts(jp, s); + } + key[k = 0] = '\0'; + break; + case '%': /* literal % */ + if (k < KEYLEN) { + key[k++] = '%'; + key[k] = '\0'; + } else + jp->error = JPRINT_FMT; + break; + default: + jp->error = JPRINT_FMT; + } + break; + case '{': /* open object */ + if (jp->stackp >= (JP_MAX_STACK - 1)) + jp->error = JPRINT_STACK_FULL; + else { + (void) jp_key(jp, key); + ++jp->stackp; + jp->stack[jp->stackp].type = JP_OBJECT; + jp->stack[jp->stackp].nelem = 0; + jp_putc(jp, '{'); + } + break; + case '}': /* close object */ + if (jp->stackp < 0) + jp->error = JPRINT_STACK_EMPTY; + else if (jp->stack[jp->stackp].type != JP_OBJECT) + jp->error = JPRINT_NEST_ERROR; + else { + --jp->stackp; + jp_putc(jp, '}'); + } + break; + case '[': /* open array */ + if (jp->stackp >= (JP_MAX_STACK - 1)) + jp->error = JPRINT_STACK_FULL; + else { + (void) jp_key(jp, key); + ++jp->stackp; + jp->stack[jp->stackp].type = JP_ARRAY; + jp->stack[jp->stackp].nelem = 0; + jp_putc(jp, '['); + } + break; + case ']': /* close array */ + if (jp->stackp < 0) + jp->error = JPRINT_STACK_EMPTY; + else if (jp->stack[jp->stackp].type != JP_ARRAY) + jp->error = JPRINT_NEST_ERROR; + else { + --jp->stackp; + jp_putc(jp, ']'); + } + break; + + case ',': /* ,: space tab are ignored */ + case ':': + case ' ': + case '\t': + break; + case '\\': + /* allow inclusion of ,: space tab to key */ + if (fmt[1] == '\0') + jp->error = JPRINT_FMT; + else { + ++fmt; + if (k < KEYLEN) { + key[k++] = *fmt; + key[k] = '\0'; + } else + jp->error = JPRINT_FMT; + } + break; + default: + if (k < KEYLEN) { + key[k++] = *fmt; + key[k] = '\0'; + } else + jp->error = JPRINT_FMT; + break; + } + ++fmt; + } + va_end(ap); + if (jp->error != JPRINT_OK) + return (-1); + + return ((int)(jp->bufp - start)); +} diff --git a/module/zfs/spa.c b/module/zfs/spa.c index d51cc4fcd0..13c4f3a028 100644 --- a/module/zfs/spa.c +++ b/module/zfs/spa.c @@ -5956,7 +5956,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; @@ -6006,7 +6006,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; @@ -6118,7 +6118,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..21c13f1a83 --- /dev/null +++ b/module/zfs/spa_json_stats.c @@ -0,0 +1,649 @@ +/* + * 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 +#include + +#define JSON_STATUS_VERSION 4 + +/* + * Return string for datatype -- this guides us in implementing + * json translation. + */ +static const char * +datatype_string(data_type_t t) +{ + switch (t) { + case DATA_TYPE_UNKNOWN: return "DATA_TYPE_UNKNOWN"; + case DATA_TYPE_BOOLEAN: return "DATA_TYPE_BOOLEAN"; + case DATA_TYPE_BYTE: return "DATA_TYPE_BYTE"; + case DATA_TYPE_INT16: return "DATA_TYPE_INT16"; + case DATA_TYPE_UINT16: return "DATA_TYPE_UINT16"; + case DATA_TYPE_INT32: return "DATA_TYPE_INT32"; + case DATA_TYPE_UINT32: return "DATA_TYPE_UINT32"; + case DATA_TYPE_INT64: return "DATA_TYPE_INT64"; + case DATA_TYPE_UINT64: return "DATA_TYPE_UINT64"; + case DATA_TYPE_STRING: return "DATA_TYPE_STRING"; + case DATA_TYPE_BYTE_ARRAY: return "DATA_TYPE_BYTE_ARRAY"; + case DATA_TYPE_INT16_ARRAY: return "DATA_TYPE_INT16_ARRAY"; + case DATA_TYPE_UINT16_ARRAY: return "DATA_TYPE_UINT16_ARRAY"; + case DATA_TYPE_INT32_ARRAY: return "DATA_TYPE_INT32_ARRAY"; + case DATA_TYPE_UINT32_ARRAY: return "DATA_TYPE_UINT32_ARRAY"; + case DATA_TYPE_INT64_ARRAY: return "DATA_TYPE_INT64_ARRAY"; + case DATA_TYPE_UINT64_ARRAY: return "DATA_TYPE_UINT64_ARRAY"; + case DATA_TYPE_STRING_ARRAY: return "DATA_TYPE_STRING_ARRAY"; + case DATA_TYPE_HRTIME: return "DATA_TYPE_HRTIME"; + case DATA_TYPE_NVLIST: return "DATA_TYPE_NVLIST"; + case DATA_TYPE_NVLIST_ARRAY: return "DATA_TYPE_NVLIST_ARRAY"; + case DATA_TYPE_BOOLEAN_VALUE: return "DATA_TYPE_BOOLEAN_VALUE"; + case DATA_TYPE_INT8: return "DATA_TYPE_INT8"; + case DATA_TYPE_UINT8: return "DATA_TYPE_UINT8"; + case DATA_TYPE_BOOLEAN_ARRAY: return "DATA_TYPE_BOOLEAN_ARRAY"; + case DATA_TYPE_INT8_ARRAY: return "DATA_TYPE_INT8_ARRAY"; + case DATA_TYPE_UINT8_ARRAY: return "DATA_TYPE_UINT8_ARRAY"; + default: return "UNKNOWN"; + } +} + +/* + * nvlist_to_json takes a filter function. If the functions returns + * B_TRUE, the case has been handled. If it returns B_FALSE, the + * case has not been handled, and will be handled. Invoking nvlist_to_json + * with a NULL filter chooses the default filter, which does nothing. + * + * The filtering is passed the jprint_t in case the nesting level is + * important, name, data type and value. + */ +typedef boolean_t nvj_filter_t(jprint_t *, const char *, data_type_t, void *); + +static boolean_t +null_filter(jprint_t *jp, const char *name, data_type_t type, void *value) +{ + jp = jp; name = name; type = type; value = value; + return (B_FALSE); +} + +static void nvlist_to_json(nvlist_t *nvl, jprint_t *jp, nvj_filter_t f); + +/* + * Convert source (src) to string -- up to 105 characters, so pass in 256 + * byte buffer (for future) + */ +static void +source_to_string(uint64_t src, char *buf) +{ + buf[0] = '\0'; + 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"); + } +} + +/* + * spa_props_filter replace source: with string. The way source is + * defined it could be bitmap -- so generate the | sequence as + * needed. + */ +static boolean_t +spa_props_filter(jprint_t *jp, const char *name, data_type_t type, void *value) +{ + if ((strcmp(name, "source") == 0) && + (type == DATA_TYPE_UINT64)) { + uint64_t src = *(uint64_t *)value; + char buf[256]; + source_to_string(src, buf); + jp_printf(jp, "source: %s", buf); + return (B_TRUE); + } + return (B_FALSE); +} + +/* + * stats_filter removes parts of the nvlist we don't want to visit. + */ +static boolean_t +stats_filter(jprint_t *jp, const char *name, data_type_t type, void *value) +{ + /* + * Suppress root object state: + */ + if ((jp->stackp == 0) && + (type == DATA_TYPE_UINT64) && + (strcmp(name, "state") == 0)) + return (B_TRUE); + + /* + * Suppress root object vdev_children: -- we will + * output at one level down. + */ + if ((jp->stackp == 0) && + (type == DATA_TYPE_UINT64) && + (strcmp(name, "vdev_children") == 0)) + return (B_TRUE); + + /* + * We are going to suppress vdev_tree:, and generate the + * data ourselves. + * It does seem like a bit of a waste going through this + * twice... but for now, this seems prudent. + */ + if ((jp->stackp == 0) && + (type == DATA_TYPE_NVLIST) && + (strcmp(name, "vdev_tree") == 0)) + return (B_TRUE); + + /* + * Process spa_props:. Here we recurse, but with a filter that + * modifies source. + */ + if ((jp->stackp == 0) && + (type == DATA_TYPE_NVLIST) && + (strcmp(name, "spa_props") == 0)) { + jp_printf(jp, "spa_props: {"); + nvlist_to_json((nvlist_t *)value, jp, spa_props_filter); + jp_printf(jp, "}"); + return (B_TRUE); + } + + return (B_FALSE); +} + +/* + * This code is NOT hightly abstracted -- just some duplication, until I + * actually understand what is needed. + * + * In avoiding early abstraction, we find the need for a "filter". Which + * is now implemented. This does appear in the spirit of other ZFS + * coding. + */ +static void +nvlist_to_json(nvlist_t *nvl, jprint_t *jp, nvj_filter_t f) +{ + const nvpriv_t *priv; + const i_nvp_t *curr; + uint64_t *u = NULL; + nvlist_t **a = NULL; + + if (f == NULL) + f = null_filter; + + if ((priv = (const nvpriv_t *)(uintptr_t)nvl->nvl_priv) == NULL) + return; + + for (curr = priv->nvp_list; curr != NULL; curr = curr->nvi_next) { + const nvpair_t *nvp = &curr->nvi_nvp; + const char *name = (const char *)NVP_NAME(nvp); + data_type_t type = NVP_TYPE(nvp); + void *p = NVP_VALUE(nvp); + + if (jp_error(jp) != JPRINT_OK) + return; + + if (f(jp, name, type, p)) + continue; + switch (type) { + + /* + * Array types + */ + case DATA_TYPE_UINT64_ARRAY: + u = (uint64_t *)p; + jp_printf(jp, "%k: [", name); + for (int i = 0; i < NVP_NELEM(nvp); ++i) { + if (jp_error(jp) != JPRINT_OK) + break; + jp_printf(jp, "%U", u[i]); + } + jp_printf(jp, "]"); + break; + case DATA_TYPE_NVLIST_ARRAY: + a = (nvlist_t **)p; + jp_printf(jp, "%k: [", name); + for (int i = 0; i < NVP_NELEM(nvp); ++i) { + if (jp_error(jp) != JPRINT_OK) + break; + jp_printf(jp, "{"); + nvlist_to_json(a[i], jp, f); + jp_printf(jp, "}"); + } + jp_printf(jp, "]"); + break; + + /* + * Primitive types + */ + case DATA_TYPE_UINT64: + jp_printf(jp, "%k: %U", name, *(uint64_t *)p); + break; + case DATA_TYPE_INT64: + jp_printf(jp, "%k: %D", name, *(int64_t *)p); + break; + case DATA_TYPE_UINT32: + jp_printf(jp, "%k: %u", name, *(uint32_t *)p); + break; + case DATA_TYPE_INT32: + jp_printf(jp, "%k: %d", name, *(int32_t *)p); + break; + case DATA_TYPE_STRING: + jp_printf(jp, "%k: %s", name, (char *)p); + break; + case DATA_TYPE_BOOLEAN: + jp_printf(jp, "%k: %b", name, B_TRUE); + break; + case DATA_TYPE_BOOLEAN_VALUE: + jp_printf(jp, "%k: %b", name, *(boolean_t *)p); + break; + + /* + * Object types + */ + case DATA_TYPE_NVLIST: + jp_printf(jp, "%k: {", name); + nvlist_to_json((nvlist_t *)p, jp, f); + jp_printf(jp, "}"); + break; + + /* + * Default -- tell us what we are missing. This is done to + * simply avoid writing ALL the cases, YAGNI (yah ain't + * gonna need it). + */ + default: + jp_printf(jp, "%k: %s", name, datatype_string(type)); + zfs_dbgmsg("name = %s type = %d %s", name, + (int)type, datatype_string(type)); + break; + } + } +} + +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_json(vdev_t *vd, pool_scan_stat_t *ps, jprint_t *jp) +{ + uint64_t i, n; + int nparity = vdev_get_nparity(vd); + vdev_t **a; + const char *s; + + jp_printf(jp, "type: %s", 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); + nvlist_to_json(nvl, jp, null_filter); + nvlist_free(nvl); + } else if (nparity > 0) { + /* RAIDZ parity */ + jp_printf(jp, "nparity: %U", nparity); + } + jp_printf(jp, "id: %U", vd->vdev_id); + jp_printf(jp, "guid: %U", vd->vdev_guid); + if (strcmp(vd->vdev_ops->vdev_op_type, "root") != 0) { + jp_printf(jp, "asize: %U", vd->vdev_asize); + jp_printf(jp, "ashift: %U", vd->vdev_ashift); + if (vd->vdev_ops->vdev_op_leaf) { + jp_printf(jp, "whole_disk: %b", + (vd->vdev_wholedisk == 0) ? B_FALSE : B_TRUE); + } + jp_printf(jp, "offline: %b", + (vd->vdev_offline == 0) ? B_FALSE : B_TRUE); + jp_printf(jp, "faulted: %b", + (vd->vdev_faulted == 0) ? B_FALSE : B_TRUE); + jp_printf(jp, "degraded: %b", + (vd->vdev_degraded == 0) ? B_FALSE : B_TRUE); + jp_printf(jp, "removed: %b", + (vd->vdev_removed == 0) ? B_FALSE : B_TRUE); + jp_printf(jp, "not_present: %b", + (vd->vdev_not_present == 0) ? B_FALSE : B_TRUE); + jp_printf(jp, "is_log: %b", + (vd->vdev_islog == 0) ? B_FALSE : B_TRUE); + + if (vd->vdev_path != NULL) + jp_printf(jp, "path: %s", vd->vdev_path); + if (vd->vdev_devid != NULL) + jp_printf(jp, "devid: %s", vd->vdev_devid); + if (vd->vdev_physpath != NULL) + jp_printf(jp, "physpath: %s", vd->vdev_physpath); + if (vd->vdev_enc_sysfs_path != NULL) + jp_printf(jp, "enc_sysfs_path: %s", + vd->vdev_enc_sysfs_path); + jp_printf(jp, "state: %s", vdev_state_string(vd->vdev_state, + vd->vdev_stat.vs_aux)); + /* + * Try for some of the extended status annotations that + * zpool status provides. + */ + jp_printf(jp, "vs_scan_removing: %b", + vd->vdev_stat.vs_scan_removing != 0); + jp_printf(jp, "vs_noalloc: %b", vd->vdev_stat.vs_noalloc != 0); + jp_printf(jp, "vs_resilver_deferred: %b", + 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"; + } + } + } + jp_printf(jp, "resilver_repair: %s", s); + jp_printf(jp, "initialize_state: {"); + 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"; + jp_printf(jp, "vs_initialize_state: %s", s); + jp_printf(jp, "vs_initialize_bytes_done: %U", + vd->vdev_stat.vs_initialize_bytes_done); + jp_printf(jp, "vs_initialize_bytes_est: %U", + vd->vdev_stat.vs_initialize_bytes_est); + jp_printf(jp, "vs_initialize_action_time: %U", + vd->vdev_stat.vs_initialize_action_time); + jp_printf(jp, "}"); + + jp_printf(jp, "trim_state: {"); + 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"; + jp_printf(jp, "vs_trim_state: %s", s); + if (!vd->vdev_stat.vs_trim_notsup) { + jp_printf(jp, "vs_trim_action_time: %U", + vd->vdev_stat.vs_trim_action_time); + jp_printf(jp, "vs_trim_bytes_done: %U", + vd->vdev_stat.vs_trim_bytes_done); + jp_printf(jp, "vs_trim_bytes_est: %U", + vd->vdev_stat.vs_trim_bytes_est); + } + jp_printf(jp, "}"); + + jp_printf(jp, "read_errors: %U", + vd->vdev_stat.vs_read_errors); + jp_printf(jp, "write_errors: %U", + vd->vdev_stat.vs_write_errors); + jp_printf(jp, "checksum_errors: %U", + vd->vdev_stat.vs_checksum_errors); + jp_printf(jp, "slow_ios: %U", + vd->vdev_stat.vs_slow_ios); + jp_printf(jp, "trim_errors: %U", + vd->vdev_stat.vs_trim_errors); + } + n = vd->vdev_children; + a = vd->vdev_child; + if (n != 0) { + jp_printf(jp, "vdev_children: %U", n); + jp_printf(jp, "children: ["); + for (i = 0; i < n; ++i) { + jp_printf(jp, "{"); + vdev_to_json(a[i], ps, jp); + jp_printf(jp, "}"); + } + jp_printf(jp, "]"); + } +} + +static void +iterate_vdevs(spa_t *spa, pool_scan_stat_t *ps, jprint_t *jp) +{ + vdev_t *v = spa->spa_root_vdev; + if (v == NULL) { + jp_printf(jp, "error: %s", "NO ROOT VDEV"); + return; + } + jp_printf(jp, "vdev_tree: {"); + vdev_to_json(v, ps, jp); + jp_printf(jp, "}"); +} + +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); +} + +/* + * 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; + jprint_t jp; + nvlist_t *nvl, *pnvl; + uint64_t loadtimes[2]; + pool_scan_stat_t ps; + int scl_config_lock; + + if (nvlist_dup(spa->spa_config, &nvl, 0) != 0) { + zfs_dbgmsg("json_data: nvlist_dup failed"); + return (0); + } + fnvlist_add_nvlist(nvl, 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); + + error = spa_prop_get(spa, &pnvl); + fnvlist_add_nvlist(nvl, "spa_props", pnvl); + + loadtimes[0] = spa->spa_loaded_ts.tv_sec; + loadtimes[1] = spa->spa_loaded_ts.tv_nsec; + fnvlist_add_uint64_array(nvl, ZPOOL_CONFIG_LOADED_TIME, loadtimes, 2); + fnvlist_add_uint64(nvl, ZPOOL_CONFIG_ERRCOUNT, + spa_approx_errlog_size(spa)); + fnvlist_add_boolean_value(nvl, 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(nvl, "failmode", failmode); + if (spa->spa_suspended != ZIO_SUSPEND_NONE) { + fnvlist_add_string(nvl, ZPOOL_CONFIG_SUSPENDED_REASON, + (spa->spa_suspended == ZIO_SUSPEND_MMP) ? + "MMP" : "IO"); + } + } + + jp_open(&jp, buf, size); + jp_printf(&jp, "{"); + + jp_printf(&jp, "status_json_version: %d", JSON_STATUS_VERSION); + jp_printf(&jp, "scl_config_lock: %b", scl_config_lock != 0); + jp_printf(&jp, "scan_error: %d", ps_error); + jp_printf(&jp, "scan_stats: {"); + if (ps_error == 0) { + jp_printf(&jp, "func: %s", pss_func_to_string(ps.pss_func)); + jp_printf(&jp, "state: %s", pss_state_to_string(ps.pss_state)); + jp_printf(&jp, "start_time: %U", ps.pss_start_time); + jp_printf(&jp, "end_time: %U", ps.pss_end_time); + jp_printf(&jp, "to_examine: %U", ps.pss_to_examine); + jp_printf(&jp, "examined: %U", ps.pss_examined); + jp_printf(&jp, "processed: %U", ps.pss_processed); + jp_printf(&jp, "errors: %U", ps.pss_errors); + + jp_printf(&jp, "pass_exam: %U", ps.pss_pass_exam); + jp_printf(&jp, "pass_start: %U", ps.pss_pass_start); + jp_printf(&jp, "pass_scrub_pause: %U", ps.pss_pass_scrub_pause); + jp_printf(&jp, "pass_scrub_spent_paused: %U", + ps.pss_pass_scrub_spent_paused); + jp_printf(&jp, "pass_issued: %U", ps.pss_pass_issued); + jp_printf(&jp, "issued: %U", ps.pss_issued); + } else if (ps_error == ENOENT) { + jp_printf(&jp, "func: %s", "NONE"); + jp_printf(&jp, "state: %s", "NONE"); + } else { + jp_printf(&jp, "func: %s", "?"); + jp_printf(&jp, "state: %s", "?"); + } + jp_printf(&jp, "}"); + + jp_printf(&jp, "state: %s", spa_state_to_name(spa)); + + spa_add_spares(spa, nvl); + spa_add_l2cache(spa, nvl); + spa_add_feature_stats(spa, nvl); + + /* iterate and transfer nvl to json */ + nvlist_to_json(nvl, &jp, stats_filter); + + iterate_vdevs(spa, &ps, &jp); + + /* + * close the root object + */ + jp_printf(&jp, "}"); + + if (scl_config_lock) + spa_config_exit(spa, SCL_CONFIG, FTAG); + nvlist_free(nvl); + + error = jp_close(&jp); + if (error == JPRINT_BUF_FULL) { + error = SET_ERROR(ENOMEM); + } else if (error != 0) { + /* + * Another error from jprint, format an error message + * but this is not ever to happen (this would be a + * defect elsewhere). + * + * If this does happen, we simply put the string where + * the json should go... this is expected to trigger + * a json decode error, and report "upstream" + */ + snprintf(buf, size, + "jprint error %s (%d) callno %d, size %ld\n", + jp_errorstring(error), error, jp_errorpos(&jp), size); + error = 0; + } + 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" From 138612c989acc00937ceb640a77ab459f367f1ba Mon Sep 17 00:00:00 2001 From: Umer Saleem Date: Thu, 15 Aug 2024 10:20:42 +0500 Subject: [PATCH 2/2] Simplify diagnostic kstat for pool status This commit updates the kstat for pool status and simplifies by creating an nvlist that contains the pool status. This nvlist is then printed to provided buffer in JSON format. The redundant parts of code have also been removed. Signed-off-by: Umer Saleem --- include/Makefile.am | 1 - include/sys/jprint.h | 70 ---- include/sys/nvpair.h | 2 + lib/libnvpair/libnvpair.abi | 7 + lib/libzpool/Makefile.am | 1 - module/Kbuild.in | 1 - module/Makefile.bsd | 1 - module/nvpair/nvpair.c | 363 +++++++++++++++++++++ module/zfs/jprint.c | 423 ------------------------- module/zfs/spa_json_stats.c | 615 +++++++++++++----------------------- 10 files changed, 588 insertions(+), 896 deletions(-) delete mode 100644 include/sys/jprint.h delete mode 100644 module/zfs/jprint.c diff --git a/include/Makefile.am b/include/Makefile.am index fcd0e51c72..504aa8592f 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -175,7 +175,6 @@ COMMON_H = \ KERNEL_H = \ - sys/jprint.h \ sys/spa_json_stats.h \ sys/zfs_ioctl.h \ sys/zfs_ioctl_impl.h \ diff --git a/include/sys/jprint.h b/include/sys/jprint.h deleted file mode 100644 index fef1a79642..0000000000 --- a/include/sys/jprint.h +++ /dev/null @@ -1,70 +0,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_JPRINT_H -#define _SYS_JPRINT_H - -/* maximum stack nesting */ -#define JP_MAX_STACK 32 - -enum jp_type { - JP_OBJECT = 1, - JP_ARRAY -}; - -struct jp_stack { - enum jp_type type; - int nelem; -}; - -typedef struct jprint { - char *buffer; /* pointer to application's buffer */ - size_t buflen; /* length of buffer */ - char *bufp; /* current write position in buffer */ - char tmpbuf[32]; /* local buffer for conversions */ - int error; /* error code */ - int ncall; /* API call number on which error occurred */ - struct jp_stack /* stack of array/object nodes */ - stack[JP_MAX_STACK]; - int stackp; -} jprint_t; - -/* error return codes */ -#define JPRINT_OK 0 /* no error */ -#define JPRINT_BUF_FULL 1 /* output buffer full */ -#define JPRINT_NEST_ERROR 2 /* nesting error */ -#define JPRINT_STACK_FULL 3 /* array/object nesting */ -#define JPRINT_STACK_EMPTY 4 /* stack underflow error */ -#define JPRINT_OPEN 5 /* not all objects closed */ -#define JPRINT_FMT 6 /* format error */ - -const char *jp_errorstring(int err); -int jp_error(jprint_t *jp); -void jp_open(jprint_t *jp, char *buffer, size_t buflen); -int jp_close(jprint_t *jp); -int jp_errorpos(jprint_t *jp); -int jp_printf(jprint_t *jp, const char *fmt, ...); - -#endif /* _SYS_JPRINT_H */ 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/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/libzpool/Makefile.am b/lib/libzpool/Makefile.am index b114fb3303..ab57d13974 100644 --- a/lib/libzpool/Makefile.am +++ b/lib/libzpool/Makefile.am @@ -112,7 +112,6 @@ nodist_libzpool_la_SOURCES = \ module/zfs/fm.c \ module/zfs/gzip.c \ module/zfs/hkdf.c \ - module/zfs/jprint.c \ module/zfs/lz4.c \ module/zfs/lz4_zfs.c \ module/zfs/lzjb.c \ diff --git a/module/Kbuild.in b/module/Kbuild.in index 7d7acfb463..5b65d85272 100644 --- a/module/Kbuild.in +++ b/module/Kbuild.in @@ -353,7 +353,6 @@ ZFS_OBJS := \ fm.o \ gzip.o \ hkdf.o \ - jprint.o \ lz4.o \ lz4_zfs.o \ lzjb.o \ diff --git a/module/Makefile.bsd b/module/Makefile.bsd index 971ada5a58..f281de92d3 100644 --- a/module/Makefile.bsd +++ b/module/Makefile.bsd @@ -282,7 +282,6 @@ SRCS+= abd.c \ edonr_zfs.c \ fm.c \ gzip.c \ - jprint.c \ lz4.c \ lz4_zfs.c \ lzjb.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/zfs/jprint.c b/module/zfs/jprint.c deleted file mode 100644 index 7070423aed..0000000000 --- a/module/zfs/jprint.c +++ /dev/null @@ -1,423 +0,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. - */ - -#include -#include - -/* Formats for int64_t and uint64_t */ -#ifndef PRId64 -#define PRId64 "lld" /* %D, int64_t */ -#define PRIu64 "llu" /* %U, uint64_t */ -#endif - -/* literal key length maximum */ -#define KEYLEN 255 - -/* return error position (call number of jp_printf) */ -int -jp_errorpos(jprint_t *jp) -{ - return (jp->ncall); -} - -/* return string for error code */ -const char * -jp_errorstring(int err) -{ - switch (err) { - case JPRINT_OK: return ("jprint ok"); - case JPRINT_BUF_FULL: return ("jprint buffer full"); - case JPRINT_NEST_ERROR: return ("jprint nest error"); - case JPRINT_STACK_FULL: return ("jprint stack full"); - case JPRINT_STACK_EMPTY: return ("jprint stack empty"); - case JPRINT_OPEN: return ("jprint open"); - case JPRINT_FMT: return ("jprint format"); - default: return ("jprint unknown error"); - } - return ("jprint unknown error"); -} - -/* return error from jprint_t */ -int -jp_error(jprint_t *jp) -{ - return (jp->error); -} - -/* open json using buffer of length buflen */ -void -jp_open(jprint_t *jp, char *buffer, size_t buflen) -{ - jp->buffer = jp->bufp = buffer; - jp->buflen = buflen; - jp->error = JPRINT_OK; - jp->ncall = 0; - jp->stackp = -1; - *buffer = '\0'; -} - - -/* close json (return out of memory error) */ -int -jp_close(jprint_t *jp) -{ - if (jp->error != JPRINT_OK) - return (jp->error); - if (jp->stackp != -1) - jp->error = JPRINT_OPEN; - return (jp->error); -} - - -/* put character to json */ -static void -jp_putc(jprint_t *jp, char c) -{ - if (jp->error == JPRINT_OK) { - if ((jp->bufp - jp->buffer + 1) >= jp->buflen) - jp->error = JPRINT_BUF_FULL; - else { - *jp->bufp++ = c; - *jp->bufp = '\0'; - } - } -} - - -/* put string to json */ -static void -jp_puts(jprint_t *jp, char *s) -{ - while (*s && (jp->error == JPRINT_OK)) - jp_putc(jp, *s++); -} - - -/* put quoted string to json */ -static void -jp_putsq(jprint_t *jp, char *s) -{ - static const char *hex = "0123456789ABCDEF"; - int c; - - if (s == NULL) { - jp_puts(jp, (char *)"null"); - return; - } - jp_putc(jp, '\"'); - while (*s && (jp->error == JPRINT_OK)) { - c = (int)*s++; - /* formfeed, newline, return, tab, backspace */ - if (c == 12) - jp_puts(jp, (char *)"\\f"); - else if (c == 10) - jp_puts(jp, (char *)"\\n"); - else if (c == 13) - jp_puts(jp, (char *)"\\r"); - else if (c == 9) - jp_puts(jp, (char *)"\\t"); - else if (c == 8) - jp_puts(jp, (char *)"\\b"); - /* - * all characters from 0x00 to 0x1f, and 0x7f are - * escaped as: \u00xx - */ - else if (((0 <= c) && (c <= 0x1f)) || (c == 0x7f)) { - jp_puts(jp, (char *)"\\u00"); - jp_putc(jp, hex[(c >> 4) & 0x0f]); - jp_putc(jp, hex[c & 0x0f]); - /* * " \ / */ - } else if (c == '"') - jp_puts(jp, (char *)"\\\""); - else if (c == '\\') - jp_puts(jp, (char *)"\\\\"); - else if (c == '/') - jp_puts(jp, (char *)"\\/"); - /* - * 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 - jp_putc(jp, (char)c); - } - jp_putc(jp, '\"'); -} - - -/* put out key if object open. error if nothing open */ -static int -jp_key(jprint_t *jp, char *key) -{ - if (jp->error != JPRINT_OK) - goto err; - /* at top level, no frame exists yet, no error */ - if (jp->stackp == -1) - goto err; - /* stackp has been "popped" too many times */ - if (jp->stackp < -1) { - jp->error = JPRINT_STACK_EMPTY; - goto err; - } - /* put comma separator in (both object and array) */ - if (++jp->stack[jp->stackp].nelem > 1) - jp_putc(jp, ','); - /* if its in an object, put out the key and separator */ - if (jp->stack[jp->stackp].type == JP_OBJECT) { - jp_putsq(jp, key); - jp_putc(jp, ':'); - } -err: - return (jp->error); -} - - -/* printf to json */ -int -jp_printf(jprint_t *jp, const char *fmt, ...) -{ - char key[KEYLEN + 1]; - int k, i; - va_list ap; - int n; - unsigned int u; - int64_t n64; - uint64_t u64; - boolean_t b; - char *s; - char *start = jp->bufp; - - if (jp->error != JPRINT_OK) - return (-1); - ++jp->ncall; - va_start(ap, fmt); - key[k = 0] = '\0'; - while (*fmt && (jp->error == JPRINT_OK)) { - /* - * If we need to debug jprint format, - * zfs_dbgmsg("====> jprint char = %c\n", *fmt); - */ - switch (*fmt) { - case '%': - ++fmt; - switch (*fmt) { - case 'k': /* next parameter is key */ - if (jp->stackp < 0) { - jp->error = JPRINT_STACK_EMPTY; - break; - } - s = va_arg(ap, char *); - if (strlen(s) <= KEYLEN) - strcpy(key, s); - else - jp->error = JPRINT_FMT; - break; - case 'd': /* next parameter is int */ - if (jp->stackp < 0) { - jp->error = JPRINT_STACK_EMPTY; - break; - } - n = va_arg(ap, int); - i = snprintf( - jp->tmpbuf, sizeof (jp->tmpbuf), - "%d", n); - if (jp_key(jp, key) == JPRINT_OK) { - if ((i >= sizeof (jp->tmpbuf)) || - (i < 0)) - jp_puts(jp, (char *)"####"); - else - jp_puts(jp, jp->tmpbuf); - } - key[k = 0] = '\0'; - break; - case 'u': /* next parameter is unsigned int */ - if (jp->stackp < 0) { - jp->error = JPRINT_STACK_EMPTY; - break; - } - u = va_arg(ap, unsigned int); - i = snprintf( - jp->tmpbuf, sizeof (jp->tmpbuf), - "%u", u); - if (jp_key(jp, key) == JPRINT_OK) { - if ((i >= sizeof (jp->tmpbuf)) || - (i < 0)) - jp_puts(jp, (char *)"####"); - else - jp_puts(jp, jp->tmpbuf); - } - key[k = 0] = '\0'; - break; - case 'U': /* next parameter is uint64_t */ - if (jp->stackp < 0) { - jp->error = JPRINT_STACK_EMPTY; - break; - } - u64 = va_arg(ap, uint64_t); - i = snprintf( - jp->tmpbuf, sizeof (jp->tmpbuf), - "%" PRIu64, u64); - if (jp_key(jp, key) == JPRINT_OK) { - if ((i >= sizeof (jp->tmpbuf)) || - (i < 0)) - jp_puts(jp, (char *)"####"); - else - jp_puts(jp, jp->tmpbuf); - } - key[k = 0] = '\0'; - break; - case 'D': /* next parameter is int64_t */ - if (jp->stackp < 0) { - jp->error = JPRINT_STACK_EMPTY; - break; - } - n64 = va_arg(ap, int64_t); - i = snprintf( - jp->tmpbuf, sizeof (jp->tmpbuf), - "%" PRId64, n64); - if (jp_key(jp, key) == JPRINT_OK) { - if ((i >= sizeof (jp->tmpbuf)) || - (i < 0)) - jp_puts(jp, (char *)"####"); - else - jp_puts(jp, jp->tmpbuf); - } - key[k = 0] = '\0'; - break; - case 's': /* next parameter is string */ - if (jp->stackp < 0) { - jp->error = JPRINT_STACK_EMPTY; - break; - } - s = va_arg(ap, char *); - if (jp_key(jp, key) == JPRINT_OK) - jp_putsq(jp, s); - key[k = 0] = '\0'; - break; - case 'b': /* next parameter is boolean */ - if (jp->stackp < 0) { - jp->error = JPRINT_STACK_EMPTY; - break; - } - if (jp_key(jp, key) == JPRINT_OK) { - b = (boolean_t)va_arg(ap, int); - s = b ? - (char *)"true" : - (char *)"false"; - jp_puts(jp, s); - } - key[k = 0] = '\0'; - break; - case '%': /* literal % */ - if (k < KEYLEN) { - key[k++] = '%'; - key[k] = '\0'; - } else - jp->error = JPRINT_FMT; - break; - default: - jp->error = JPRINT_FMT; - } - break; - case '{': /* open object */ - if (jp->stackp >= (JP_MAX_STACK - 1)) - jp->error = JPRINT_STACK_FULL; - else { - (void) jp_key(jp, key); - ++jp->stackp; - jp->stack[jp->stackp].type = JP_OBJECT; - jp->stack[jp->stackp].nelem = 0; - jp_putc(jp, '{'); - } - break; - case '}': /* close object */ - if (jp->stackp < 0) - jp->error = JPRINT_STACK_EMPTY; - else if (jp->stack[jp->stackp].type != JP_OBJECT) - jp->error = JPRINT_NEST_ERROR; - else { - --jp->stackp; - jp_putc(jp, '}'); - } - break; - case '[': /* open array */ - if (jp->stackp >= (JP_MAX_STACK - 1)) - jp->error = JPRINT_STACK_FULL; - else { - (void) jp_key(jp, key); - ++jp->stackp; - jp->stack[jp->stackp].type = JP_ARRAY; - jp->stack[jp->stackp].nelem = 0; - jp_putc(jp, '['); - } - break; - case ']': /* close array */ - if (jp->stackp < 0) - jp->error = JPRINT_STACK_EMPTY; - else if (jp->stack[jp->stackp].type != JP_ARRAY) - jp->error = JPRINT_NEST_ERROR; - else { - --jp->stackp; - jp_putc(jp, ']'); - } - break; - - case ',': /* ,: space tab are ignored */ - case ':': - case ' ': - case '\t': - break; - case '\\': - /* allow inclusion of ,: space tab to key */ - if (fmt[1] == '\0') - jp->error = JPRINT_FMT; - else { - ++fmt; - if (k < KEYLEN) { - key[k++] = *fmt; - key[k] = '\0'; - } else - jp->error = JPRINT_FMT; - } - break; - default: - if (k < KEYLEN) { - key[k++] = *fmt; - key[k] = '\0'; - } else - jp->error = JPRINT_FMT; - break; - } - ++fmt; - } - va_end(ap); - if (jp->error != JPRINT_OK) - return (-1); - - return ((int)(jp->bufp - start)); -} diff --git a/module/zfs/spa_json_stats.c b/module/zfs/spa_json_stats.c index 21c13f1a83..51700484ac 100644 --- a/module/zfs/spa_json_stats.c +++ b/module/zfs/spa_json_stats.c @@ -28,284 +28,11 @@ #include #include #include -#include #include #include #define JSON_STATUS_VERSION 4 -/* - * Return string for datatype -- this guides us in implementing - * json translation. - */ -static const char * -datatype_string(data_type_t t) -{ - switch (t) { - case DATA_TYPE_UNKNOWN: return "DATA_TYPE_UNKNOWN"; - case DATA_TYPE_BOOLEAN: return "DATA_TYPE_BOOLEAN"; - case DATA_TYPE_BYTE: return "DATA_TYPE_BYTE"; - case DATA_TYPE_INT16: return "DATA_TYPE_INT16"; - case DATA_TYPE_UINT16: return "DATA_TYPE_UINT16"; - case DATA_TYPE_INT32: return "DATA_TYPE_INT32"; - case DATA_TYPE_UINT32: return "DATA_TYPE_UINT32"; - case DATA_TYPE_INT64: return "DATA_TYPE_INT64"; - case DATA_TYPE_UINT64: return "DATA_TYPE_UINT64"; - case DATA_TYPE_STRING: return "DATA_TYPE_STRING"; - case DATA_TYPE_BYTE_ARRAY: return "DATA_TYPE_BYTE_ARRAY"; - case DATA_TYPE_INT16_ARRAY: return "DATA_TYPE_INT16_ARRAY"; - case DATA_TYPE_UINT16_ARRAY: return "DATA_TYPE_UINT16_ARRAY"; - case DATA_TYPE_INT32_ARRAY: return "DATA_TYPE_INT32_ARRAY"; - case DATA_TYPE_UINT32_ARRAY: return "DATA_TYPE_UINT32_ARRAY"; - case DATA_TYPE_INT64_ARRAY: return "DATA_TYPE_INT64_ARRAY"; - case DATA_TYPE_UINT64_ARRAY: return "DATA_TYPE_UINT64_ARRAY"; - case DATA_TYPE_STRING_ARRAY: return "DATA_TYPE_STRING_ARRAY"; - case DATA_TYPE_HRTIME: return "DATA_TYPE_HRTIME"; - case DATA_TYPE_NVLIST: return "DATA_TYPE_NVLIST"; - case DATA_TYPE_NVLIST_ARRAY: return "DATA_TYPE_NVLIST_ARRAY"; - case DATA_TYPE_BOOLEAN_VALUE: return "DATA_TYPE_BOOLEAN_VALUE"; - case DATA_TYPE_INT8: return "DATA_TYPE_INT8"; - case DATA_TYPE_UINT8: return "DATA_TYPE_UINT8"; - case DATA_TYPE_BOOLEAN_ARRAY: return "DATA_TYPE_BOOLEAN_ARRAY"; - case DATA_TYPE_INT8_ARRAY: return "DATA_TYPE_INT8_ARRAY"; - case DATA_TYPE_UINT8_ARRAY: return "DATA_TYPE_UINT8_ARRAY"; - default: return "UNKNOWN"; - } -} - -/* - * nvlist_to_json takes a filter function. If the functions returns - * B_TRUE, the case has been handled. If it returns B_FALSE, the - * case has not been handled, and will be handled. Invoking nvlist_to_json - * with a NULL filter chooses the default filter, which does nothing. - * - * The filtering is passed the jprint_t in case the nesting level is - * important, name, data type and value. - */ -typedef boolean_t nvj_filter_t(jprint_t *, const char *, data_type_t, void *); - -static boolean_t -null_filter(jprint_t *jp, const char *name, data_type_t type, void *value) -{ - jp = jp; name = name; type = type; value = value; - return (B_FALSE); -} - -static void nvlist_to_json(nvlist_t *nvl, jprint_t *jp, nvj_filter_t f); - -/* - * Convert source (src) to string -- up to 105 characters, so pass in 256 - * byte buffer (for future) - */ -static void -source_to_string(uint64_t src, char *buf) -{ - buf[0] = '\0'; - 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"); - } -} - -/* - * spa_props_filter replace source: with string. The way source is - * defined it could be bitmap -- so generate the | sequence as - * needed. - */ -static boolean_t -spa_props_filter(jprint_t *jp, const char *name, data_type_t type, void *value) -{ - if ((strcmp(name, "source") == 0) && - (type == DATA_TYPE_UINT64)) { - uint64_t src = *(uint64_t *)value; - char buf[256]; - source_to_string(src, buf); - jp_printf(jp, "source: %s", buf); - return (B_TRUE); - } - return (B_FALSE); -} - -/* - * stats_filter removes parts of the nvlist we don't want to visit. - */ -static boolean_t -stats_filter(jprint_t *jp, const char *name, data_type_t type, void *value) -{ - /* - * Suppress root object state: - */ - if ((jp->stackp == 0) && - (type == DATA_TYPE_UINT64) && - (strcmp(name, "state") == 0)) - return (B_TRUE); - - /* - * Suppress root object vdev_children: -- we will - * output at one level down. - */ - if ((jp->stackp == 0) && - (type == DATA_TYPE_UINT64) && - (strcmp(name, "vdev_children") == 0)) - return (B_TRUE); - - /* - * We are going to suppress vdev_tree:, and generate the - * data ourselves. - * It does seem like a bit of a waste going through this - * twice... but for now, this seems prudent. - */ - if ((jp->stackp == 0) && - (type == DATA_TYPE_NVLIST) && - (strcmp(name, "vdev_tree") == 0)) - return (B_TRUE); - - /* - * Process spa_props:. Here we recurse, but with a filter that - * modifies source. - */ - if ((jp->stackp == 0) && - (type == DATA_TYPE_NVLIST) && - (strcmp(name, "spa_props") == 0)) { - jp_printf(jp, "spa_props: {"); - nvlist_to_json((nvlist_t *)value, jp, spa_props_filter); - jp_printf(jp, "}"); - return (B_TRUE); - } - - return (B_FALSE); -} - -/* - * This code is NOT hightly abstracted -- just some duplication, until I - * actually understand what is needed. - * - * In avoiding early abstraction, we find the need for a "filter". Which - * is now implemented. This does appear in the spirit of other ZFS - * coding. - */ -static void -nvlist_to_json(nvlist_t *nvl, jprint_t *jp, nvj_filter_t f) -{ - const nvpriv_t *priv; - const i_nvp_t *curr; - uint64_t *u = NULL; - nvlist_t **a = NULL; - - if (f == NULL) - f = null_filter; - - if ((priv = (const nvpriv_t *)(uintptr_t)nvl->nvl_priv) == NULL) - return; - - for (curr = priv->nvp_list; curr != NULL; curr = curr->nvi_next) { - const nvpair_t *nvp = &curr->nvi_nvp; - const char *name = (const char *)NVP_NAME(nvp); - data_type_t type = NVP_TYPE(nvp); - void *p = NVP_VALUE(nvp); - - if (jp_error(jp) != JPRINT_OK) - return; - - if (f(jp, name, type, p)) - continue; - switch (type) { - - /* - * Array types - */ - case DATA_TYPE_UINT64_ARRAY: - u = (uint64_t *)p; - jp_printf(jp, "%k: [", name); - for (int i = 0; i < NVP_NELEM(nvp); ++i) { - if (jp_error(jp) != JPRINT_OK) - break; - jp_printf(jp, "%U", u[i]); - } - jp_printf(jp, "]"); - break; - case DATA_TYPE_NVLIST_ARRAY: - a = (nvlist_t **)p; - jp_printf(jp, "%k: [", name); - for (int i = 0; i < NVP_NELEM(nvp); ++i) { - if (jp_error(jp) != JPRINT_OK) - break; - jp_printf(jp, "{"); - nvlist_to_json(a[i], jp, f); - jp_printf(jp, "}"); - } - jp_printf(jp, "]"); - break; - - /* - * Primitive types - */ - case DATA_TYPE_UINT64: - jp_printf(jp, "%k: %U", name, *(uint64_t *)p); - break; - case DATA_TYPE_INT64: - jp_printf(jp, "%k: %D", name, *(int64_t *)p); - break; - case DATA_TYPE_UINT32: - jp_printf(jp, "%k: %u", name, *(uint32_t *)p); - break; - case DATA_TYPE_INT32: - jp_printf(jp, "%k: %d", name, *(int32_t *)p); - break; - case DATA_TYPE_STRING: - jp_printf(jp, "%k: %s", name, (char *)p); - break; - case DATA_TYPE_BOOLEAN: - jp_printf(jp, "%k: %b", name, B_TRUE); - break; - case DATA_TYPE_BOOLEAN_VALUE: - jp_printf(jp, "%k: %b", name, *(boolean_t *)p); - break; - - /* - * Object types - */ - case DATA_TYPE_NVLIST: - jp_printf(jp, "%k: {", name); - nvlist_to_json((nvlist_t *)p, jp, f); - jp_printf(jp, "}"); - break; - - /* - * Default -- tell us what we are missing. This is done to - * simply avoid writing ALL the cases, YAGNI (yah ain't - * gonna need it). - */ - default: - jp_printf(jp, "%k: %s", name, datatype_string(type)); - zfs_dbgmsg("name = %s type = %d %s", name, - (int)type, datatype_string(type)); - break; - } - } -} - static const char * vdev_state_string(vdev_state_t state, vdev_aux_t aux) { @@ -332,67 +59,71 @@ vdev_state_string(vdev_state_t state, vdev_aux_t aux) } static void -vdev_to_json(vdev_t *vd, pool_scan_stat_t *ps, jprint_t *jp) +vdev_to_nvlist(vdev_t *vd, pool_scan_stat_t *ps, nvlist_t *tree) { - uint64_t i, n; + uint64_t n; int nparity = vdev_get_nparity(vd); vdev_t **a; const char *s; + nvlist_t *init_state, *trim_state; - jp_printf(jp, "type: %s", vd->vdev_ops->vdev_op_type); + 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); - nvlist_to_json(nvl, jp, null_filter); + fnvlist_merge(tree, nvl); nvlist_free(nvl); } else if (nparity > 0) { /* RAIDZ parity */ - jp_printf(jp, "nparity: %U", nparity); + fnvlist_add_uint64(tree, "nparity", nparity); } - jp_printf(jp, "id: %U", vd->vdev_id); - jp_printf(jp, "guid: %U", vd->vdev_guid); + + 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) { - jp_printf(jp, "asize: %U", vd->vdev_asize); - jp_printf(jp, "ashift: %U", vd->vdev_ashift); + fnvlist_add_uint64(tree, "asize", vd->vdev_asize); + fnvlist_add_uint64(tree, "ashift", vd->vdev_ashift); if (vd->vdev_ops->vdev_op_leaf) { - jp_printf(jp, "whole_disk: %b", + fnvlist_add_boolean_value(tree, "whole_disk", (vd->vdev_wholedisk == 0) ? B_FALSE : B_TRUE); } - jp_printf(jp, "offline: %b", + fnvlist_add_boolean_value(tree, "offline", (vd->vdev_offline == 0) ? B_FALSE : B_TRUE); - jp_printf(jp, "faulted: %b", + fnvlist_add_boolean_value(tree, "faulted", (vd->vdev_faulted == 0) ? B_FALSE : B_TRUE); - jp_printf(jp, "degraded: %b", + fnvlist_add_boolean_value(tree, "degraded", (vd->vdev_degraded == 0) ? B_FALSE : B_TRUE); - jp_printf(jp, "removed: %b", + fnvlist_add_boolean_value(tree, "removed", (vd->vdev_removed == 0) ? B_FALSE : B_TRUE); - jp_printf(jp, "not_present: %b", + fnvlist_add_boolean_value(tree, "not_present", (vd->vdev_not_present == 0) ? B_FALSE : B_TRUE); - jp_printf(jp, "is_log: %b", + fnvlist_add_boolean_value(tree, "is_log", (vd->vdev_islog == 0) ? B_FALSE : B_TRUE); if (vd->vdev_path != NULL) - jp_printf(jp, "path: %s", vd->vdev_path); + fnvlist_add_string(tree, "path", vd->vdev_path); if (vd->vdev_devid != NULL) - jp_printf(jp, "devid: %s", vd->vdev_devid); + fnvlist_add_string(tree, "devid", vd->vdev_devid); if (vd->vdev_physpath != NULL) - jp_printf(jp, "physpath: %s", vd->vdev_physpath); - if (vd->vdev_enc_sysfs_path != NULL) - jp_printf(jp, "enc_sysfs_path: %s", + 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); - jp_printf(jp, "state: %s", vdev_state_string(vd->vdev_state, - vd->vdev_stat.vs_aux)); + } + 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. */ - jp_printf(jp, "vs_scan_removing: %b", + fnvlist_add_boolean_value(tree, "vs_scan_removing", vd->vdev_stat.vs_scan_removing != 0); - jp_printf(jp, "vs_noalloc: %b", vd->vdev_stat.vs_noalloc != 0); - jp_printf(jp, "vs_resilver_deferred: %b", + 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) || @@ -409,8 +140,9 @@ vdev_to_json(vdev_t *vd, pool_scan_stat_t *ps, jprint_t *jp) } } } - jp_printf(jp, "resilver_repair: %s", s); - jp_printf(jp, "initialize_state: {"); + 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"; @@ -420,16 +152,17 @@ vdev_to_json(vdev_t *vd, pool_scan_stat_t *ps, jprint_t *jp) if (vd->vdev_stat.vs_initialize_state == VDEV_INITIALIZE_COMPLETE) s = "VDEV_INITIALIZE_COMPLETE"; - jp_printf(jp, "vs_initialize_state: %s", s); - jp_printf(jp, "vs_initialize_bytes_done: %U", + 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); - jp_printf(jp, "vs_initialize_bytes_est: %U", + fnvlist_add_uint64(init_state, "vs_initialize_bytes_est", vd->vdev_stat.vs_initialize_bytes_est); - jp_printf(jp, "vs_initialize_action_time: %U", + fnvlist_add_uint64(init_state, "vs_initialize_action_time", vd->vdev_stat.vs_initialize_action_time); - jp_printf(jp, "}"); + fnvlist_add_nvlist(tree, "initialize_state", init_state); + fnvlist_free(init_state); - jp_printf(jp, "trim_state: {"); + trim_state = fnvlist_alloc(); s = "VDEV_UNTRIMMED"; if (vd->vdev_stat.vs_trim_state == VDEV_TRIM_ACTIVE) s = "VDEV_TRIM_ACTIVE"; @@ -439,53 +172,88 @@ vdev_to_json(vdev_t *vd, pool_scan_stat_t *ps, jprint_t *jp) s = "VDEV_TRIM_COMPLETE"; if (vd->vdev_stat.vs_trim_notsup) s = "VDEV_TRIM_UNSUPPORTED"; - jp_printf(jp, "vs_trim_state: %s", s); + fnvlist_add_string(trim_state, "vs_trim_state", s); if (!vd->vdev_stat.vs_trim_notsup) { - jp_printf(jp, "vs_trim_action_time: %U", + fnvlist_add_uint64(trim_state, "vs_trim_action_time", vd->vdev_stat.vs_trim_action_time); - jp_printf(jp, "vs_trim_bytes_done: %U", + fnvlist_add_uint64(trim_state, "vs_trim_bytes_done", vd->vdev_stat.vs_trim_bytes_done); - jp_printf(jp, "vs_trim_bytes_est: %U", + fnvlist_add_uint64(trim_state, "vs_trim_bytes_est", vd->vdev_stat.vs_trim_bytes_est); } - jp_printf(jp, "}"); + fnvlist_add_nvlist(tree, "trim_state", trim_state); + fnvlist_free(trim_state); - jp_printf(jp, "read_errors: %U", + fnvlist_add_uint64(tree, "read_errors", vd->vdev_stat.vs_read_errors); - jp_printf(jp, "write_errors: %U", + fnvlist_add_uint64(tree, "write_errors", vd->vdev_stat.vs_write_errors); - jp_printf(jp, "checksum_errors: %U", + fnvlist_add_uint64(tree, "checksum_errors", vd->vdev_stat.vs_checksum_errors); - jp_printf(jp, "slow_ios: %U", + fnvlist_add_uint64(tree, "slow_ios", vd->vdev_stat.vs_slow_ios); - jp_printf(jp, "trim_errors: %U", + fnvlist_add_uint64(tree, "trim_errors", vd->vdev_stat.vs_trim_errors); } n = vd->vdev_children; a = vd->vdev_child; if (n != 0) { - jp_printf(jp, "vdev_children: %U", n); - jp_printf(jp, "children: ["); - for (i = 0; i < n; ++i) { - jp_printf(jp, "{"); - vdev_to_json(a[i], ps, jp); - jp_printf(jp, "}"); + 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]); } - jp_printf(jp, "]"); + 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, jprint_t *jp) +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) { - jp_printf(jp, "error: %s", "NO ROOT VDEV"); + zfs_dbgmsg("error: NO ROOT VDEV"); return; } - jp_printf(jp, "vdev_tree: {"); - vdev_to_json(v, ps, jp); - jp_printf(jp, "}"); + 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 * @@ -514,6 +282,68 @@ static const char *pss_state_to_string(uint64_t n) 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. * @@ -524,32 +354,36 @@ spa_generate_json_stats(spa_t *spa, char *buf, size_t size) { int error = 0; int ps_error = 0; - jprint_t jp; - nvlist_t *nvl, *pnvl; + 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; - if (nvlist_dup(spa->spa_config, &nvl, 0) != 0) { + 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(nvl, ZPOOL_CONFIG_LOAD_INFO, spa->spa_load_info); + 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; - error = spa_prop_get(spa, &pnvl); - fnvlist_add_nvlist(nvl, "spa_props", pnvl); + 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(nvl, ZPOOL_CONFIG_LOADED_TIME, loadtimes, 2); - fnvlist_add_uint64(nvl, ZPOOL_CONFIG_ERRCOUNT, + 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(nvl, ZPOOL_CONFIG_SUSPENDED, + fnvlist_add_boolean_value(spa_config, ZPOOL_CONFIG_SUSPENDED, spa_suspended(spa)); if (spa_suspended(spa)) { const char *failmode; @@ -566,84 +400,67 @@ spa_generate_json_stats(spa_t *spa, char *buf, size_t size) default: failmode = "???"; } - fnvlist_add_string(nvl, "failmode", failmode); + fnvlist_add_string(spa_config, "failmode", failmode); if (spa->spa_suspended != ZIO_SUSPEND_NONE) { - fnvlist_add_string(nvl, ZPOOL_CONFIG_SUSPENDED_REASON, + fnvlist_add_string(spa_config, + ZPOOL_CONFIG_SUSPENDED_REASON, (spa->spa_suspended == ZIO_SUSPEND_MMP) ? "MMP" : "IO"); } } - jp_open(&jp, buf, size); - jp_printf(&jp, "{"); + 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); - jp_printf(&jp, "status_json_version: %d", JSON_STATUS_VERSION); - jp_printf(&jp, "scl_config_lock: %b", scl_config_lock != 0); - jp_printf(&jp, "scan_error: %d", ps_error); - jp_printf(&jp, "scan_stats: {"); + scan_stats = fnvlist_alloc(); if (ps_error == 0) { - jp_printf(&jp, "func: %s", pss_func_to_string(ps.pss_func)); - jp_printf(&jp, "state: %s", pss_state_to_string(ps.pss_state)); - jp_printf(&jp, "start_time: %U", ps.pss_start_time); - jp_printf(&jp, "end_time: %U", ps.pss_end_time); - jp_printf(&jp, "to_examine: %U", ps.pss_to_examine); - jp_printf(&jp, "examined: %U", ps.pss_examined); - jp_printf(&jp, "processed: %U", ps.pss_processed); - jp_printf(&jp, "errors: %U", ps.pss_errors); - - jp_printf(&jp, "pass_exam: %U", ps.pss_pass_exam); - jp_printf(&jp, "pass_start: %U", ps.pss_pass_start); - jp_printf(&jp, "pass_scrub_pause: %U", ps.pss_pass_scrub_pause); - jp_printf(&jp, "pass_scrub_spent_paused: %U", + 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); - jp_printf(&jp, "pass_issued: %U", ps.pss_pass_issued); - jp_printf(&jp, "issued: %U", ps.pss_issued); + 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) { - jp_printf(&jp, "func: %s", "NONE"); - jp_printf(&jp, "state: %s", "NONE"); + fnvlist_add_string(scan_stats, "func", "NONE"); + fnvlist_add_string(scan_stats, "state", "NONE"); } else { - jp_printf(&jp, "func: %s", "?"); - jp_printf(&jp, "state: %s", "?"); + fnvlist_add_string(scan_stats, "func", "NONE"); + fnvlist_add_string(scan_stats, "state", "NONE"); } - jp_printf(&jp, "}"); + fnvlist_add_nvlist(nvl, "scan_stats", scan_stats); + fnvlist_add_string(nvl, "state", spa_state_to_name(spa)); - jp_printf(&jp, "state: %s", 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); - spa_add_spares(spa, nvl); - spa_add_l2cache(spa, nvl); - spa_add_feature_stats(spa, nvl); - - /* iterate and transfer nvl to json */ - nvlist_to_json(nvl, &jp, stats_filter); - - iterate_vdevs(spa, &ps, &jp); - - /* - * close the root object - */ - jp_printf(&jp, "}"); + /* 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); - nvlist_free(nvl); - error = jp_close(&jp); - if (error == JPRINT_BUF_FULL) { - error = SET_ERROR(ENOMEM); - } else if (error != 0) { - /* - * Another error from jprint, format an error message - * but this is not ever to happen (this would be a - * defect elsewhere). - * - * If this does happen, we simply put the string where - * the json should go... this is expected to trigger - * a json decode error, and report "upstream" - */ - snprintf(buf, size, - "jprint error %s (%d) callno %d, size %ld\n", - jp_errorstring(error), error, jp_errorpos(&jp), size); - error = 0; - } + error = nvlist_to_json(nvl, &curr, size); + nvlist_free(nvl); + nvlist_free(spa_config); + nvlist_free(spa_props); + nvlist_free(scan_stats); + return (error); }