root/usr/src/cmd/stat/kstat/kstat.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2013 David Hoeppner. All rights reserved.
 * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
 * Copyright 2016 Joyent, Inc.
 * Copyright 2020 Peter Tribble.
 * Copyright 2025 Edgecast Cloud LLC.
 */

/*
 * Display kernel statistics
 *
 * This is a reimplementation of the perl kstat command originally found
 * under usr/src/cmd/kstat/kstat.pl
 *
 * Incompatibilities:
 *      - perl regular expressions replaced with extended REs bracketed by '/'
 *
 * Flags added:
 *      -C      similar to the -p option but value is separated by a colon
 *      -h      display help
 *      -j      json format
 */

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <kstat.h>
#include <langinfo.h>
#include <libgen.h>
#include <limits.h>
#include <locale.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <sys/list.h>
#include <sys/time.h>
#include <sys/types.h>

#include "kstat.h"
#include "statcommon.h"

char    *cmdname = "kstat";     /* Name of this command */
int     caught_cont = 0;        /* Have caught a SIGCONT */

static uint_t   g_timestamp_fmt = NODATE;

/* Helper flag - header was printed already? */
static boolean_t g_headerflg;

/* Saved command line options */
static boolean_t g_cflg = B_FALSE;
static boolean_t g_jflg = B_FALSE;
static boolean_t g_lflg = B_FALSE;
static boolean_t g_pflg = B_FALSE;
static boolean_t g_qflg = B_FALSE;
static boolean_t g_Vflg = B_FALSE;
static ks_pattern_t     g_ks_class = {"*", 0};

static boolean_t g_matched = B_FALSE;

/* Sorted list of kstat instances */
static list_t   instances_list;
static list_t   selector_list;

static boolean_t
invalid_value(char *str)
{
        /* Empty string is same as "*" */
        if (str == NULL)
                return (B_TRUE);
        if (strchr(str, '*') != NULL)
                return (B_TRUE);

        /* regex is enclosed between // */
        if (str[0] == '/') {
                size_t s = strlen(str);
                if (str[s - 1] == '/')
                        return (B_TRUE);
        }
        return (B_FALSE);
}

static boolean_t
invalid_values(ks_selector_t *selector)
{
        /*
         * -V needs exact match of module and statistics or
         *  name and statistics.
         */
        if ((!invalid_value(selector->ks_module.pstr) ||
            !invalid_value(selector->ks_name.pstr)) &&
            !invalid_value(selector->ks_statistic.pstr))
                return (B_FALSE);

        (void) fprintf(stderr, gettext(
            "-V needs exact module or name, and statistics\n"));
        return (B_TRUE);
}

int
main(int argc, char **argv)
{
        ks_selector_t   *nselector;
        ks_selector_t   *uselector;
        kstat_ctl_t     *kc;
        hrtime_t        start_n;
        hrtime_t        period_n;
        boolean_t       errflg = B_FALSE;
        boolean_t       nselflg = B_FALSE;
        boolean_t       uselflg = B_FALSE;
        char            *q;
        int             count = 1;
        int             infinite_cycles = 0;
        int             interval = 0;
        int             n = 0;
        int             c, m, tmp;

        (void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)               /* Should be defined by cc -D */
#define TEXT_DOMAIN "SYS_TEST"          /* Use this only if it wasn't */
#endif
        (void) textdomain(TEXT_DOMAIN);

        /*
         * Create the selector list and a dummy default selector to match
         * everything. While we process the cmdline options we will add
         * selectors to this list.
         */
        list_create(&selector_list, sizeof (ks_selector_t),
            offsetof(ks_selector_t, ks_next));

        nselector = new_selector();

        /*
         * Parse named command line arguments.
         */
        while ((c = getopt(argc, argv, "h?CqjlpT:Vm:i:n:s:c:")) != EOF)
                switch (c) {
                case 'h':
                case '?':
                        usage();
                        exit(0);
                        break;
                case 'C':
                        g_pflg = g_cflg = B_TRUE;
                        break;
                case 'q':
                        g_qflg = B_TRUE;
                        break;
                case 'j':
                        /*
                         * If we're printing JSON, we're going to force numeric
                         * representation to be in the C locale to assure that
                         * the decimal point is compliant with RFC 7159 (i.e.,
                         * ASCII 0x2e).
                         */
                        (void) setlocale(LC_NUMERIC, "C");
                        g_jflg = B_TRUE;
                        break;
                case 'l':
                        g_pflg = g_lflg = B_TRUE;
                        break;
                case 'p':
                        g_pflg = B_TRUE;
                        break;
                case 'T':
                        switch (*optarg) {
                        case 'd':
                                g_timestamp_fmt = DDATE;
                                break;
                        case 'u':
                                g_timestamp_fmt = UDATE;
                                break;
                        default:
                                errflg = B_TRUE;
                        }
                        break;
                case 'V':
                        g_Vflg = B_TRUE;
                        break;
                case 'm':
                        nselflg = B_TRUE;
                        nselector->ks_module.pstr =
                            (char *)ks_safe_strdup(optarg);
                        break;
                case 'i':
                        nselflg = B_TRUE;
                        nselector->ks_instance.pstr =
                            (char *)ks_safe_strdup(optarg);
                        break;
                case 'n':
                        nselflg = B_TRUE;
                        nselector->ks_name.pstr =
                            (char *)ks_safe_strdup(optarg);
                        break;
                case 's':
                        nselflg = B_TRUE;
                        nselector->ks_statistic.pstr =
                            (char *)ks_safe_strdup(optarg);
                        break;
                case 'c':
                        g_ks_class.pstr =
                            (char *)ks_safe_strdup(optarg);
                        break;
                default:
                        errflg = B_TRUE;
                        break;
                }

        if (g_Vflg && !g_pflg) {
                (void) fprintf(stderr, gettext(
                    "-V can only be used with -p\n"));
                errflg = B_TRUE;
        }
        if (g_Vflg && (g_timestamp_fmt != NODATE ||
            g_lflg || g_jflg || g_cflg)) {
                (void) fprintf(stderr, gettext(
                    "-jlCT and -V are mutually exclusive\n"));
                errflg = B_TRUE;
        }
        if (g_qflg && (g_jflg || g_pflg)) {
                (void) fprintf(stderr, gettext(
                    "-q and -lpj are mutually exclusive\n"));
                errflg = B_TRUE;
        }

        if (errflg) {
                usage();
                exit(2);
        }

        argc -= optind;
        argv += optind;

        /*
         * Consume the rest of the command line. Parsing the
         * unnamed command line arguments.
         */
        while (argc--) {
                errno = 0;
                tmp = strtoul(*argv, &q, 10);
                if (tmp == ULONG_MAX && errno == ERANGE) {
                        if (n == 0) {
                                (void) fprintf(stderr, gettext(
                                    "Interval is too large\n"));
                        } else if (n == 1) {
                                (void) fprintf(stderr, gettext(
                                    "Count is too large\n"));
                        }
                        usage();
                        exit(2);
                }

                if (errno != 0 || *q != '\0') {
                        m = 0;
                        uselector = new_selector();
                        while ((q = (char *)strsep(argv, ":")) != NULL) {
                                m++;
                                if (m > 4) {
                                        free(uselector);
                                        usage();
                                        exit(2);
                                }

                                if (*q != '\0') {
                                        switch (m) {
                                        case 1:
                                                uselector->ks_module.pstr =
                                                    (char *)ks_safe_strdup(q);
                                                break;
                                        case 2:
                                                uselector->ks_instance.pstr =
                                                    (char *)ks_safe_strdup(q);
                                                break;
                                        case 3:
                                                uselector->ks_name.pstr =
                                                    (char *)ks_safe_strdup(q);
                                                break;
                                        case 4:
                                                uselector->ks_statistic.pstr =
                                                    (char *)ks_safe_strdup(q);
                                                break;
                                        default:
                                                assert(B_FALSE);
                                        }
                                }
                        }

                        /*
                         * With -V, only do allow exact matches.
                         */
                        if (g_Vflg && invalid_values(uselector)) {
                                exit(2);
                        }
                        uselflg = B_TRUE;
                        list_insert_tail(&selector_list, uselector);
                } else {
                        if (tmp < 1) {
                                if (n == 0) {
                                        (void) fprintf(stderr, gettext(
                                            "Interval must be an "
                                            "integer >= 1"));
                                } else if (n == 1) {
                                        (void) fprintf(stderr, gettext(
                                            "Count must be an integer >= 1"));
                                }
                                usage();
                                exit(2);
                        } else {
                                if (n == 0) {
                                        interval = tmp;
                                        count = -1;
                                } else if (n == 1) {
                                        count = tmp;
                                } else {
                                        usage();
                                        exit(2);
                                }
                        }
                        n++;
                }
                argv++;
        }

        /*
         * Check if we founded a named selector on the cmdline.
         */
        if (uselflg) {
                if (nselflg) {
                        (void) fprintf(stderr, gettext(
                            "[module[:instance[:name[:statistic]]]] and "
                            "-m -i -n -s are mutually exclusive"));
                        usage();
                        exit(2);
                } else {
                        free(nselector);
                }
        } else {
                /*
                 * With -V, only do allow exact matches.
                 */
                if (g_Vflg && invalid_values(nselector)) {
                        exit(2);
                }
                list_insert_tail(&selector_list, nselector);
        }

        assert(!list_is_empty(&selector_list));

        list_create(&instances_list, sizeof (ks_instance_t),
            offsetof(ks_instance_t, ks_next));

        while ((kc = kstat_open()) == NULL) {
                if (errno == EAGAIN) {
                        (void) poll(NULL, 0, 200);
                } else {
                        perror("kstat_open");
                        exit(3);
                }
        }

        if (count > 1) {
                if (signal(SIGCONT, cont_handler) == SIG_ERR) {
                        (void) fprintf(stderr, gettext(
                            "signal failed"));
                        exit(3);
                }
        }

        period_n = (hrtime_t)interval * NANOSEC;
        start_n = gethrtime();

        while (count == -1 || count-- > 0) {
                ks_instances_read(kc);
                ks_instances_print();

                if (interval && count) {
                        ks_sleep_until(&start_n, period_n, infinite_cycles,
                            &caught_cont);
                        (void) kstat_chain_update(kc);
                        (void) putchar('\n');
                }
        }

        (void) kstat_close(kc);

        /*
         * Return a non-zero exit code if we didn't match anything.
         */
        return (g_matched ? 0 : 1);
}

/*
 * Print usage.
 */
static void
usage(void)
{
        (void) fprintf(stderr, gettext(
            "Usage:\n"
            "kstat [ -Cjlq ] [ -p [ -V ]] [ -T d|u ] [ -c class ]\n"
            "      [ -m module ] [ -i instance ] [ -n name ] [ -s statistic ]\n"
            "      [ interval [ count ] ]\n"
            "kstat [ -Cjlq ] [ -p [ -V ]] [ -T d|u ] [ -c class ]\n"
            "      [ module[:instance[:name[:statistic]]] ... ]\n"
            "      [ interval [ count ] ]\n"));
}

/*
 * Sort compare function.
 */
static int
compare_instances(ks_instance_t *l_arg, ks_instance_t *r_arg)
{
        int     rval;

        rval = strcasecmp(l_arg->ks_module, r_arg->ks_module);
        if (rval == 0) {
                if (l_arg->ks_instance == r_arg->ks_instance) {
                        return (strcasecmp(l_arg->ks_name, r_arg->ks_name));
                } else if (l_arg->ks_instance < r_arg->ks_instance) {
                        return (-1);
                } else {
                        return (1);
                }
        } else {
                return (rval);
        }
}

static char *
ks_safe_strdup(char *str)
{
        char    *ret;

        if (str == NULL) {
                return (NULL);
        }

        while ((ret = strdup(str)) == NULL) {
                if (errno == EAGAIN) {
                        (void) poll(NULL, 0, 200);
                } else {
                        perror("strdup");
                        exit(3);
                }
        }

        return (ret);
}

static void
ks_sleep_until(hrtime_t *wakeup, hrtime_t interval, int forever,
    int *caught_cont)
{
        hrtime_t        now, pause, pause_left;
        struct timespec pause_tv;
        int             status;

        now = gethrtime();
        pause = *wakeup + interval - now;

        if (pause <= 0 || pause < (interval / 4)) {
                if (forever || *caught_cont) {
                        *wakeup = now + interval;
                        pause = interval;
                } else {
                        pause = interval / 2;
                        *wakeup += interval;
                }
        } else {
                *wakeup += interval;
        }

        if (pause < 1000) {
                return;
        }

        pause_left = pause;
        do {
                pause_tv.tv_sec = pause_left / NANOSEC;
                pause_tv.tv_nsec = pause_left % NANOSEC;
                status = nanosleep(&pause_tv, (struct timespec *)NULL);
                if (status < 0) {
                        if (errno == EINTR) {
                                now = gethrtime();
                                pause_left = *wakeup - now;
                                if (pause_left < 1000) {
                                        return;
                                }
                        } else {
                                perror("nanosleep");
                                exit(3);
                        }
                }
        } while (status != 0);
}

/*
 * Inserts an instance in the per selector list.
 */
static void
nvpair_insert(ks_instance_t *ksi, char *name, ks_value_t *value,
    uchar_t data_type)
{
        ks_nvpair_t     *instance;
        ks_nvpair_t     *tmp;

        instance = (ks_nvpair_t *)malloc(sizeof (ks_nvpair_t));
        if (instance == NULL) {
                perror("malloc");
                exit(3);
        }

        (void) strlcpy(instance->name, name, KSTAT_STRLEN);
        (void) memcpy(&instance->value, value, sizeof (ks_value_t));
        instance->data_type = data_type;

        tmp = list_head(&ksi->ks_nvlist);
        while (tmp != NULL && strcasecmp(instance->name, tmp->name) > 0)
                tmp = list_next(&ksi->ks_nvlist, tmp);

        list_insert_before(&ksi->ks_nvlist, tmp, instance);
}

/*
 * Allocates a new all-matching selector.
 */
static ks_selector_t *
new_selector(void)
{
        ks_selector_t   *selector;

        selector = (ks_selector_t *)malloc(sizeof (ks_selector_t));
        if (selector == NULL) {
                perror("malloc");
                exit(3);
        }

        list_link_init(&selector->ks_next);

        selector->ks_module.pstr = "*";
        selector->ks_instance.pstr = "*";
        selector->ks_name.pstr = "*";
        selector->ks_statistic.pstr = "*";

        return (selector);
}

/*
 * This function was taken from the perl kstat module code - please
 * see for further comments there.
 */
static kstat_raw_reader_t
lookup_raw_kstat_fn(char *module, char *name)
{
        char            key[KSTAT_STRLEN * 2];
        register char   *f, *t;
        int             n = 0;

        for (f = module, t = key; *f != '\0'; f++, t++) {
                while (*f != '\0' && isdigit(*f))
                        f++;
                *t = *f;
        }
        *t++ = ':';

        for (f = name; *f != '\0'; f++, t++) {
                while (*f != '\0' && isdigit(*f))
                        f++;
                *t = *f;
        }
        *t = '\0';

        while (ks_raw_lookup[n].fn != NULL) {
                if (strncmp(ks_raw_lookup[n].name, key, strlen(key)) == 0)
                        return (ks_raw_lookup[n].fn);
                n++;
        }

        return (0);
}

/*
 * Match a string against a shell glob or extended regular expression.
 */
static boolean_t
ks_match(const char *str, ks_pattern_t *pattern)
{
        int     regcode;
        char    *regstr;
        char    *errbuf;
        size_t  bufsz;

        if (pattern->pstr != NULL && gmatch(pattern->pstr, "/*/") != 0) {
                /* All regex patterns are strdup'd copies */
                regstr = pattern->pstr + 1;
                *(strrchr(regstr, '/')) = '\0';

                regcode = regcomp(&pattern->preg, regstr,
                    REG_EXTENDED | REG_NOSUB);
                if (regcode != 0) {
                        bufsz = regerror(regcode, NULL, NULL, 0);
                        if (bufsz != 0) {
                                errbuf = malloc(bufsz);
                                if (errbuf == NULL) {
                                        perror("malloc");
                                        exit(3);
                                }
                                (void) regerror(regcode, NULL, errbuf, bufsz);
                                (void) fprintf(stderr, "kstat: %s\n", errbuf);
                        }
                        usage();
                        exit(2);
                }

                pattern->pstr = NULL;
        }

        if (pattern->pstr == NULL) {
                return (regexec(&pattern->preg, str, 0, NULL, 0) == 0);
        }

        return ((gmatch(str, pattern->pstr) != 0));
}

/*
 * Iterate over all kernel statistics and save matches.
 */
static void
ks_instances_read(kstat_ctl_t *kc)
{
        kstat_raw_reader_t save_raw = NULL;
        kid_t           id;
        ks_selector_t   *selector;
        ks_instance_t   *ksi;
        ks_instance_t   *tmp;
        kstat_t         *kp;
        boolean_t       skip;

        for (kp = kc->kc_chain; kp != NULL; kp = kp->ks_next) {
                /* Don't bother storing the kstat headers */
                if (strncmp(kp->ks_name, "kstat_", 6) == 0) {
                        continue;
                }

                /* Don't bother storing raw stats we don't understand */
                if (kp->ks_type == KSTAT_TYPE_RAW) {
                        save_raw = lookup_raw_kstat_fn(kp->ks_module,
                            kp->ks_name);
                        if (save_raw == NULL) {
#ifdef REPORT_UNKNOWN
                                (void) fprintf(stderr,
                                    "Unknown kstat type %s:%d:%s - "
                                    "%d of size %d\n", kp->ks_module,
                                    kp->ks_instance, kp->ks_name,
                                    kp->ks_ndata, kp->ks_data_size);
#endif
                                continue;
                        }
                }

                /*
                 * Iterate over the list of selectors and skip
                 * instances we dont want. We filter for statistics
                 * later, as we dont know them yet.
                 */
                skip = B_TRUE;
                selector = list_head(&selector_list);
                while (selector != NULL) {
                        if (ks_match(kp->ks_module, &selector->ks_module) &&
                            ks_match(kp->ks_name, &selector->ks_name)) {
                                skip = B_FALSE;
                                break;
                        }
                        selector = list_next(&selector_list, selector);
                }

                if (skip) {
                        continue;
                }

                /*
                 * Allocate a new instance and fill in the values
                 * we know so far.
                 */
                ksi = (ks_instance_t *)malloc(sizeof (ks_instance_t));
                if (ksi == NULL) {
                        perror("malloc");
                        exit(3);
                }

                list_link_init(&ksi->ks_next);

                (void) strlcpy(ksi->ks_module, kp->ks_module, KSTAT_STRLEN);
                (void) strlcpy(ksi->ks_name, kp->ks_name, KSTAT_STRLEN);
                (void) strlcpy(ksi->ks_class, kp->ks_class, KSTAT_STRLEN);

                ksi->ks_instance = kp->ks_instance;
                ksi->ks_snaptime = kp->ks_snaptime;
                ksi->ks_type = kp->ks_type;

                list_create(&ksi->ks_nvlist, sizeof (ks_nvpair_t),
                    offsetof(ks_nvpair_t, nv_next));

                SAVE_HRTIME_X(ksi, "crtime", kp->ks_crtime);
                if (g_pflg) {
                        SAVE_STRING_X(ksi, "class", kp->ks_class);
                }

                /* Insert this instance into a sorted list */
                tmp = list_head(&instances_list);
                while (tmp != NULL && compare_instances(ksi, tmp) > 0)
                        tmp = list_next(&instances_list, tmp);

                list_insert_before(&instances_list, tmp, ksi);

                /* Read the actual statistics */
                id = kstat_read(kc, kp, NULL);
                if (id == -1) {
#ifdef REPORT_UNKNOWN
                        perror("kstat_read");
#endif
                        continue;
                }

                SAVE_HRTIME_X(ksi, "snaptime", kp->ks_snaptime);

                switch (kp->ks_type) {
                case KSTAT_TYPE_RAW:
                        save_raw(kp, ksi);
                        break;
                case KSTAT_TYPE_NAMED:
                        save_named(kp, ksi);
                        break;
                case KSTAT_TYPE_INTR:
                        save_intr(kp, ksi);
                        break;
                case KSTAT_TYPE_IO:
                        save_io(kp, ksi);
                        break;
                case KSTAT_TYPE_TIMER:
                        save_timer(kp, ksi);
                        break;
                default:
                        assert(B_FALSE); /* Invalid type */
                        break;
                }
        }
}

/*
 * Print the value of a name-value pair.
 */
static void
ks_value_print(ks_nvpair_t *nvpair)
{
        switch (nvpair->data_type) {
        case KSTAT_DATA_CHAR:
                (void) fprintf(stdout, "%s", nvpair->value.c);
                break;
        case KSTAT_DATA_INT32:
                (void) fprintf(stdout, "%d", nvpair->value.i32);
                break;
        case KSTAT_DATA_UINT32:
                (void) fprintf(stdout, "%u", nvpair->value.ui32);
                break;
        case KSTAT_DATA_INT64:
                (void) fprintf(stdout, "%lld", nvpair->value.i64);
                break;
        case KSTAT_DATA_UINT64:
                (void) fprintf(stdout, "%llu", nvpair->value.ui64);
                break;
        case KSTAT_DATA_STRING:
                (void) fprintf(stdout, "%s", KSTAT_NAMED_STR_PTR(nvpair));
                break;
        case KSTAT_DATA_HRTIME:
                if (nvpair->value.ui64 == 0)
                        (void) fprintf(stdout, "0");
                else
                        (void) fprintf(stdout, "%.9f",
                            nvpair->value.ui64 / 1000000000.0);
                break;
        default:
                assert(B_FALSE);
        }
}

/*
 * Print a single instance.
 */
/*ARGSUSED*/
static void
ks_instance_print(ks_instance_t *ksi, ks_nvpair_t *nvpair, boolean_t last)
{
        if (g_headerflg) {
                if (!g_pflg) {
                        (void) fprintf(stdout, DFLT_FMT,
                            ksi->ks_module, ksi->ks_instance,
                            ksi->ks_name, ksi->ks_class);
                }
                g_headerflg = B_FALSE;
        }

        if (g_pflg) {
                if (!g_Vflg) {
                        (void) fprintf(stdout, KS_PFMT,
                            ksi->ks_module, ksi->ks_instance,
                            ksi->ks_name, nvpair->name);
                }
                if (!g_lflg) {
                        if (!g_Vflg)
                                (void) putchar(g_cflg ? ':': '\t');
                        ks_value_print(nvpair);
                }
        } else {
                (void) fprintf(stdout, KS_DFMT, nvpair->name);
                ks_value_print(nvpair);
        }

        (void) putchar('\n');
}

/*
 * Print a C string as a JSON string.
 */
static void
ks_print_json_string(const char *str)
{
        char c;

        (void) putchar('"');

        while ((c = *str++) != '\0') {
                /*
                 * For readability, we use the allowed alternate escape
                 * sequence for quote, question mark, reverse solidus (look
                 * it up!), newline and tab -- and use the universal escape
                 * sequence for all other control characters.
                 */
                switch (c) {
                case '"':
                case '?':
                case '\\':
                        (void) fprintf(stdout, "\\%c", c);
                        break;

                case '\n':
                        (void) fprintf(stdout, "\\n");
                        break;

                case '\t':
                        (void) fprintf(stdout, "\\t");
                        break;

                default:
                        /*
                         * By escaping those characters for which isprint(3C)
                         * is false, we escape both the RFC 7159 mandated
                         * escaped range of 0x01 through 0x1f as well as DEL
                         * (0x7f -- the control character that RFC 7159 forgot)
                         * and then everything else that's unprintable for
                         * good measure.
                         */
                        if (!isprint(c)) {
                                (void) fprintf(stdout, "\\u%04hhx", (uint8_t)c);
                                break;
                        }

                        (void) putchar(c);
                        break;
                }
        }

        (void) putchar('"');
}

/*
 * Print a single instance in JSON format.
 */
static void
ks_instance_print_json(ks_instance_t *ksi, ks_nvpair_t *nvpair, boolean_t last)
{
        static int headers;

        if (g_headerflg) {
                if (headers++ > 0)
                        (void) fprintf(stdout, ", ");

                (void) fprintf(stdout, "{\n\t\"module\": ");
                ks_print_json_string(ksi->ks_module);

                (void) fprintf(stdout,
                    ",\n\t\"instance\": %d,\n\t\"name\": ", ksi->ks_instance);
                ks_print_json_string(ksi->ks_name);

                (void) fprintf(stdout, ",\n\t\"class\": ");
                ks_print_json_string(ksi->ks_class);

                (void) fprintf(stdout, ",\n\t\"type\": %d,\n", ksi->ks_type);

                if (ksi->ks_snaptime == 0)
                        (void) fprintf(stdout, "\t\"snaptime\": 0,\n");
                else
                        (void) fprintf(stdout, "\t\"snaptime\": %.9f,\n",
                            ksi->ks_snaptime / 1000000000.0);

                (void) fprintf(stdout, "\t\"data\": {\n");

                g_headerflg = B_FALSE;
        }

        (void) fprintf(stdout, "\t\t");
        ks_print_json_string(nvpair->name);
        (void) fprintf(stdout, ": ");

        switch (nvpair->data_type) {
        case KSTAT_DATA_CHAR:
                ks_print_json_string(nvpair->value.c);
                break;

        case KSTAT_DATA_STRING:
                ks_print_json_string(KSTAT_NAMED_STR_PTR(nvpair));
                break;

        default:
                ks_value_print(nvpair);
                break;
        }

        if (!last)
                (void) putchar(',');

        (void) putchar('\n');
}

/*
 * Print all instances.
 */
static void
ks_instances_print(void)
{
        ks_selector_t *selector;
        ks_instance_t *ksi, *ktmp;
        ks_nvpair_t *nvpair, *ntmp, *next;
        void (*ks_print_fn)(ks_instance_t *, ks_nvpair_t *, boolean_t);
        char *ks_number;

        if (g_timestamp_fmt != NODATE)
                print_timestamp(g_timestamp_fmt);

        if (g_jflg) {
                ks_print_fn = &ks_instance_print_json;
                (void) putchar('[');
        } else {
                ks_print_fn = &ks_instance_print;
        }

        /* Iterate over each selector */
        selector = list_head(&selector_list);
        while (selector != NULL) {

                /* Iterate over each instance */
                for (ksi = list_head(&instances_list); ksi != NULL;
                    ksi = list_next(&instances_list, ksi)) {

                        (void) asprintf(&ks_number, "%d", ksi->ks_instance);
                        if (!(ks_match(ksi->ks_module, &selector->ks_module) &&
                            ks_match(ksi->ks_name, &selector->ks_name) &&
                            ks_match(ks_number, &selector->ks_instance) &&
                            ks_match(ksi->ks_class, &g_ks_class))) {
                                free(ks_number);
                                continue;
                        }

                        free(ks_number);

                        g_headerflg = B_TRUE;

                        /*
                         * Find our first statistic to print.
                         */
                        for (nvpair = list_head(&ksi->ks_nvlist);
                            nvpair != NULL;
                            nvpair = list_next(&ksi->ks_nvlist, nvpair)) {
                                if (ks_match(nvpair->name,
                                    &selector->ks_statistic))
                                        break;
                        }

                        while (nvpair != NULL) {
                                boolean_t last;

                                /*
                                 * Find the next statistic to print so we can
                                 * indicate to the print function if this
                                 * statistic is the last to be printed for
                                 * this instance.
                                 */
                                for (next = list_next(&ksi->ks_nvlist, nvpair);
                                    next != NULL;
                                    next = list_next(&ksi->ks_nvlist, next)) {
                                        if (ks_match(next->name,
                                            &selector->ks_statistic))
                                                break;
                                }

                                g_matched = B_TRUE;
                                last = next == NULL ? B_TRUE : B_FALSE;

                                if (!g_qflg)
                                        (*ks_print_fn)(ksi, nvpair, last);

                                nvpair = next;
                        }

                        if (!g_headerflg) {
                                if (g_jflg) {
                                        (void) fprintf(stdout, "\t}\n}");
                                } else if (!g_pflg) {
                                        (void) putchar('\n');
                                }
                        }
                }

                selector = list_next(&selector_list, selector);
        }

        if (g_jflg)
                (void) fprintf(stdout, "]\n");

        (void) fflush(stdout);

        /* Free the instances list */
        ksi = list_head(&instances_list);
        while (ksi != NULL) {
                nvpair = list_head(&ksi->ks_nvlist);
                while (nvpair != NULL) {
                        ntmp = nvpair;
                        nvpair = list_next(&ksi->ks_nvlist, nvpair);
                        list_remove(&ksi->ks_nvlist, ntmp);
                        if (ntmp->data_type == KSTAT_DATA_STRING)
                                free(ntmp->value.str.addr.ptr);
                        free(ntmp);
                }

                ktmp = ksi;
                ksi = list_next(&instances_list, ksi);
                list_remove(&instances_list, ktmp);
                list_destroy(&ktmp->ks_nvlist);
                free(ktmp);
        }
}

static void
save_cpu_stat(kstat_t *kp, ks_instance_t *ksi)
{
        cpu_stat_t      *stat;
        cpu_sysinfo_t   *sysinfo;
        cpu_syswait_t   *syswait;
        cpu_vminfo_t    *vminfo;

        stat = (cpu_stat_t *)(kp->ks_data);
        sysinfo = &stat->cpu_sysinfo;
        syswait = &stat->cpu_syswait;
        vminfo  = &stat->cpu_vminfo;

        SAVE_UINT32_X(ksi, "idle", sysinfo->cpu[CPU_IDLE]);
        SAVE_UINT32_X(ksi, "user", sysinfo->cpu[CPU_USER]);
        SAVE_UINT32_X(ksi, "kernel", sysinfo->cpu[CPU_KERNEL]);
        SAVE_UINT32_X(ksi, "wait", sysinfo->cpu[CPU_WAIT]);
        SAVE_UINT32_X(ksi, "wait_io", sysinfo->wait[W_IO]);
        SAVE_UINT32_X(ksi, "wait_swap", sysinfo->wait[W_SWAP]);
        SAVE_UINT32_X(ksi, "wait_pio", sysinfo->wait[W_PIO]);
        SAVE_UINT32(ksi, sysinfo, bread);
        SAVE_UINT32(ksi, sysinfo, bwrite);
        SAVE_UINT32(ksi, sysinfo, lread);
        SAVE_UINT32(ksi, sysinfo, lwrite);
        SAVE_UINT32(ksi, sysinfo, phread);
        SAVE_UINT32(ksi, sysinfo, phwrite);
        SAVE_UINT32(ksi, sysinfo, pswitch);
        SAVE_UINT32(ksi, sysinfo, trap);
        SAVE_UINT32(ksi, sysinfo, intr);
        SAVE_UINT32(ksi, sysinfo, syscall);
        SAVE_UINT32(ksi, sysinfo, sysread);
        SAVE_UINT32(ksi, sysinfo, syswrite);
        SAVE_UINT32(ksi, sysinfo, sysfork);
        SAVE_UINT32(ksi, sysinfo, sysvfork);
        SAVE_UINT32(ksi, sysinfo, sysexec);
        SAVE_UINT32(ksi, sysinfo, readch);
        SAVE_UINT32(ksi, sysinfo, writech);
        SAVE_UINT32(ksi, sysinfo, rcvint);
        SAVE_UINT32(ksi, sysinfo, xmtint);
        SAVE_UINT32(ksi, sysinfo, mdmint);
        SAVE_UINT32(ksi, sysinfo, rawch);
        SAVE_UINT32(ksi, sysinfo, canch);
        SAVE_UINT32(ksi, sysinfo, outch);
        SAVE_UINT32(ksi, sysinfo, msg);
        SAVE_UINT32(ksi, sysinfo, sema);
        SAVE_UINT32(ksi, sysinfo, namei);
        SAVE_UINT32(ksi, sysinfo, ufsiget);
        SAVE_UINT32(ksi, sysinfo, ufsdirblk);
        SAVE_UINT32(ksi, sysinfo, ufsipage);
        SAVE_UINT32(ksi, sysinfo, ufsinopage);
        SAVE_UINT32(ksi, sysinfo, inodeovf);
        SAVE_UINT32(ksi, sysinfo, fileovf);
        SAVE_UINT32(ksi, sysinfo, procovf);
        SAVE_UINT32(ksi, sysinfo, intrthread);
        SAVE_UINT32(ksi, sysinfo, intrblk);
        SAVE_UINT32(ksi, sysinfo, idlethread);
        SAVE_UINT32(ksi, sysinfo, inv_swtch);
        SAVE_UINT32(ksi, sysinfo, nthreads);
        SAVE_UINT32(ksi, sysinfo, cpumigrate);
        SAVE_UINT32(ksi, sysinfo, xcalls);
        SAVE_UINT32(ksi, sysinfo, mutex_adenters);
        SAVE_UINT32(ksi, sysinfo, rw_rdfails);
        SAVE_UINT32(ksi, sysinfo, rw_wrfails);
        SAVE_UINT32(ksi, sysinfo, modload);
        SAVE_UINT32(ksi, sysinfo, modunload);
        SAVE_UINT32(ksi, sysinfo, bawrite);
#ifdef  STATISTICS      /* see header file */
        SAVE_UINT32(ksi, sysinfo, rw_enters);
        SAVE_UINT32(ksi, sysinfo, win_uo_cnt);
        SAVE_UINT32(ksi, sysinfo, win_uu_cnt);
        SAVE_UINT32(ksi, sysinfo, win_so_cnt);
        SAVE_UINT32(ksi, sysinfo, win_su_cnt);
        SAVE_UINT32(ksi, sysinfo, win_suo_cnt);
#endif

        SAVE_INT32(ksi, syswait, iowait);
        SAVE_INT32(ksi, syswait, swap);
        SAVE_INT32(ksi, syswait, physio);

        SAVE_UINT32(ksi, vminfo, pgrec);
        SAVE_UINT32(ksi, vminfo, pgfrec);
        SAVE_UINT32(ksi, vminfo, pgin);
        SAVE_UINT32(ksi, vminfo, pgpgin);
        SAVE_UINT32(ksi, vminfo, pgout);
        SAVE_UINT32(ksi, vminfo, pgpgout);
        SAVE_UINT32(ksi, vminfo, swapin);
        SAVE_UINT32(ksi, vminfo, pgswapin);
        SAVE_UINT32(ksi, vminfo, swapout);
        SAVE_UINT32(ksi, vminfo, pgswapout);
        SAVE_UINT32(ksi, vminfo, zfod);
        SAVE_UINT32(ksi, vminfo, dfree);
        SAVE_UINT32(ksi, vminfo, scan);
        SAVE_UINT32(ksi, vminfo, rev);
        SAVE_UINT32(ksi, vminfo, hat_fault);
        SAVE_UINT32(ksi, vminfo, as_fault);
        SAVE_UINT32(ksi, vminfo, maj_fault);
        SAVE_UINT32(ksi, vminfo, cow_fault);
        SAVE_UINT32(ksi, vminfo, prot_fault);
        SAVE_UINT32(ksi, vminfo, softlock);
        SAVE_UINT32(ksi, vminfo, kernel_asflt);
        SAVE_UINT32(ksi, vminfo, pgrrun);
        SAVE_UINT32(ksi, vminfo, execpgin);
        SAVE_UINT32(ksi, vminfo, execpgout);
        SAVE_UINT32(ksi, vminfo, execfree);
        SAVE_UINT32(ksi, vminfo, anonpgin);
        SAVE_UINT32(ksi, vminfo, anonpgout);
        SAVE_UINT32(ksi, vminfo, anonfree);
        SAVE_UINT32(ksi, vminfo, fspgin);
        SAVE_UINT32(ksi, vminfo, fspgout);
        SAVE_UINT32(ksi, vminfo, fsfree);
}

static void
save_var(kstat_t *kp, ks_instance_t *ksi)
{
        struct var      *var = (struct var *)(kp->ks_data);

        assert(kp->ks_data_size == sizeof (struct var));

        SAVE_INT32(ksi, var, v_buf);
        SAVE_INT32(ksi, var, v_call);
        SAVE_INT32(ksi, var, v_proc);
        SAVE_INT32(ksi, var, v_maxupttl);
        SAVE_INT32(ksi, var, v_nglobpris);
        SAVE_INT32(ksi, var, v_maxsyspri);
        SAVE_INT32(ksi, var, v_clist);
        SAVE_INT32(ksi, var, v_maxup);
        SAVE_INT32(ksi, var, v_hbuf);
        SAVE_INT32(ksi, var, v_hmask);
        SAVE_INT32(ksi, var, v_pbuf);
        SAVE_INT32(ksi, var, v_sptmap);
        SAVE_INT32(ksi, var, v_maxpmem);
        SAVE_INT32(ksi, var, v_autoup);
        SAVE_INT32(ksi, var, v_bufhwm);
}

static void
save_ncstats(kstat_t *kp, ks_instance_t *ksi)
{
        struct ncstats  *ncstats = (struct ncstats *)(kp->ks_data);

        assert(kp->ks_data_size == sizeof (struct ncstats));

        SAVE_INT32(ksi, ncstats, hits);
        SAVE_INT32(ksi, ncstats, misses);
        SAVE_INT32(ksi, ncstats, enters);
        SAVE_INT32(ksi, ncstats, dbl_enters);
        SAVE_INT32(ksi, ncstats, long_enter);
        SAVE_INT32(ksi, ncstats, long_look);
        SAVE_INT32(ksi, ncstats, move_to_front);
        SAVE_INT32(ksi, ncstats, purges);
}

static void
save_sysinfo(kstat_t *kp, ks_instance_t *ksi)
{
        sysinfo_t       *sysinfo = (sysinfo_t *)(kp->ks_data);

        assert(kp->ks_data_size == sizeof (sysinfo_t));

        SAVE_UINT32(ksi, sysinfo, updates);
        SAVE_UINT32(ksi, sysinfo, runque);
        SAVE_UINT32(ksi, sysinfo, runocc);
        SAVE_UINT32(ksi, sysinfo, swpque);
        SAVE_UINT32(ksi, sysinfo, swpocc);
        SAVE_UINT32(ksi, sysinfo, waiting);
}

static void
save_vminfo(kstat_t *kp, ks_instance_t *ksi)
{
        vminfo_t        *vminfo = (vminfo_t *)(kp->ks_data);

        assert(kp->ks_data_size == sizeof (vminfo_t));

        SAVE_UINT64(ksi, vminfo, freemem);
        SAVE_UINT64(ksi, vminfo, swap_resv);
        SAVE_UINT64(ksi, vminfo, swap_alloc);
        SAVE_UINT64(ksi, vminfo, swap_avail);
        SAVE_UINT64(ksi, vminfo, swap_free);
        SAVE_UINT64(ksi, vminfo, updates);
}

static void
save_nfs(kstat_t *kp, ks_instance_t *ksi)
{
        struct mntinfo_kstat *mntinfo = (struct mntinfo_kstat *)(kp->ks_data);

        assert(kp->ks_data_size == sizeof (struct mntinfo_kstat));

        SAVE_STRING(ksi, mntinfo, mik_proto);
        SAVE_UINT32(ksi, mntinfo, mik_vers);
        SAVE_UINT32(ksi, mntinfo, mik_flags);
        SAVE_UINT32(ksi, mntinfo, mik_secmod);
        SAVE_UINT32(ksi, mntinfo, mik_curread);
        SAVE_UINT32(ksi, mntinfo, mik_curwrite);
        SAVE_INT32(ksi, mntinfo, mik_timeo);
        SAVE_INT32(ksi, mntinfo, mik_retrans);
        SAVE_UINT32(ksi, mntinfo, mik_acregmin);
        SAVE_UINT32(ksi, mntinfo, mik_acregmax);
        SAVE_UINT32(ksi, mntinfo, mik_acdirmin);
        SAVE_UINT32(ksi, mntinfo, mik_acdirmax);
        SAVE_UINT32_X(ksi, "lookup_srtt", mntinfo->mik_timers[0].srtt);
        SAVE_UINT32_X(ksi, "lookup_deviate", mntinfo->mik_timers[0].deviate);
        SAVE_UINT32_X(ksi, "lookup_rtxcur", mntinfo->mik_timers[0].rtxcur);
        SAVE_UINT32_X(ksi, "read_srtt", mntinfo->mik_timers[1].srtt);
        SAVE_UINT32_X(ksi, "read_deviate", mntinfo->mik_timers[1].deviate);
        SAVE_UINT32_X(ksi, "read_rtxcur", mntinfo->mik_timers[1].rtxcur);
        SAVE_UINT32_X(ksi, "write_srtt", mntinfo->mik_timers[2].srtt);
        SAVE_UINT32_X(ksi, "write_deviate", mntinfo->mik_timers[2].deviate);
        SAVE_UINT32_X(ksi, "write_rtxcur", mntinfo->mik_timers[2].rtxcur);
        SAVE_UINT32(ksi, mntinfo, mik_noresponse);
        SAVE_UINT32(ksi, mntinfo, mik_failover);
        SAVE_UINT32(ksi, mntinfo, mik_remap);
        SAVE_STRING(ksi, mntinfo, mik_curserver);
}

#ifdef __sparc
static void
save_sfmmu_global_stat(kstat_t *kp, ks_instance_t *ksi)
{
        struct sfmmu_global_stat *sfmmug =
            (struct sfmmu_global_stat *)(kp->ks_data);

        assert(kp->ks_data_size == sizeof (struct sfmmu_global_stat));

        SAVE_INT32(ksi, sfmmug, sf_tsb_exceptions);
        SAVE_INT32(ksi, sfmmug, sf_tsb_raise_exception);
        SAVE_INT32(ksi, sfmmug, sf_pagefaults);
        SAVE_INT32(ksi, sfmmug, sf_uhash_searches);
        SAVE_INT32(ksi, sfmmug, sf_uhash_links);
        SAVE_INT32(ksi, sfmmug, sf_khash_searches);
        SAVE_INT32(ksi, sfmmug, sf_khash_links);
        SAVE_INT32(ksi, sfmmug, sf_swapout);
        SAVE_INT32(ksi, sfmmug, sf_tsb_alloc);
        SAVE_INT32(ksi, sfmmug, sf_tsb_allocfail);
        SAVE_INT32(ksi, sfmmug, sf_tsb_sectsb_create);
        SAVE_INT32(ksi, sfmmug, sf_scd_1sttsb_alloc);
        SAVE_INT32(ksi, sfmmug, sf_scd_2ndtsb_alloc);
        SAVE_INT32(ksi, sfmmug, sf_scd_1sttsb_allocfail);
        SAVE_INT32(ksi, sfmmug, sf_scd_2ndtsb_allocfail);
        SAVE_INT32(ksi, sfmmug, sf_tteload8k);
        SAVE_INT32(ksi, sfmmug, sf_tteload64k);
        SAVE_INT32(ksi, sfmmug, sf_tteload512k);
        SAVE_INT32(ksi, sfmmug, sf_tteload4m);
        SAVE_INT32(ksi, sfmmug, sf_tteload32m);
        SAVE_INT32(ksi, sfmmug, sf_tteload256m);
        SAVE_INT32(ksi, sfmmug, sf_tsb_load8k);
        SAVE_INT32(ksi, sfmmug, sf_tsb_load4m);
        SAVE_INT32(ksi, sfmmug, sf_hblk_hit);
        SAVE_INT32(ksi, sfmmug, sf_hblk8_ncreate);
        SAVE_INT32(ksi, sfmmug, sf_hblk8_nalloc);
        SAVE_INT32(ksi, sfmmug, sf_hblk1_ncreate);
        SAVE_INT32(ksi, sfmmug, sf_hblk1_nalloc);
        SAVE_INT32(ksi, sfmmug, sf_hblk_slab_cnt);
        SAVE_INT32(ksi, sfmmug, sf_hblk_reserve_cnt);
        SAVE_INT32(ksi, sfmmug, sf_hblk_recurse_cnt);
        SAVE_INT32(ksi, sfmmug, sf_hblk_reserve_hit);
        SAVE_INT32(ksi, sfmmug, sf_get_free_success);
        SAVE_INT32(ksi, sfmmug, sf_get_free_throttle);
        SAVE_INT32(ksi, sfmmug, sf_get_free_fail);
        SAVE_INT32(ksi, sfmmug, sf_put_free_success);
        SAVE_INT32(ksi, sfmmug, sf_put_free_fail);
        SAVE_INT32(ksi, sfmmug, sf_pgcolor_conflict);
        SAVE_INT32(ksi, sfmmug, sf_uncache_conflict);
        SAVE_INT32(ksi, sfmmug, sf_unload_conflict);
        SAVE_INT32(ksi, sfmmug, sf_ism_uncache);
        SAVE_INT32(ksi, sfmmug, sf_ism_recache);
        SAVE_INT32(ksi, sfmmug, sf_recache);
        SAVE_INT32(ksi, sfmmug, sf_steal_count);
        SAVE_INT32(ksi, sfmmug, sf_pagesync);
        SAVE_INT32(ksi, sfmmug, sf_clrwrt);
        SAVE_INT32(ksi, sfmmug, sf_pagesync_invalid);
        SAVE_INT32(ksi, sfmmug, sf_kernel_xcalls);
        SAVE_INT32(ksi, sfmmug, sf_user_xcalls);
        SAVE_INT32(ksi, sfmmug, sf_tsb_grow);
        SAVE_INT32(ksi, sfmmug, sf_tsb_shrink);
        SAVE_INT32(ksi, sfmmug, sf_tsb_resize_failures);
        SAVE_INT32(ksi, sfmmug, sf_tsb_reloc);
        SAVE_INT32(ksi, sfmmug, sf_user_vtop);
        SAVE_INT32(ksi, sfmmug, sf_ctx_inv);
        SAVE_INT32(ksi, sfmmug, sf_tlb_reprog_pgsz);
        SAVE_INT32(ksi, sfmmug, sf_region_remap_demap);
        SAVE_INT32(ksi, sfmmug, sf_create_scd);
        SAVE_INT32(ksi, sfmmug, sf_join_scd);
        SAVE_INT32(ksi, sfmmug, sf_leave_scd);
        SAVE_INT32(ksi, sfmmug, sf_destroy_scd);
}
#endif

#ifdef __sparc
static void
save_sfmmu_tsbsize_stat(kstat_t *kp, ks_instance_t *ksi)
{
        struct sfmmu_tsbsize_stat *sfmmut;

        assert(kp->ks_data_size == sizeof (struct sfmmu_tsbsize_stat));
        sfmmut = (struct sfmmu_tsbsize_stat *)(kp->ks_data);

        SAVE_INT32(ksi, sfmmut, sf_tsbsz_8k);
        SAVE_INT32(ksi, sfmmut, sf_tsbsz_16k);
        SAVE_INT32(ksi, sfmmut, sf_tsbsz_32k);
        SAVE_INT32(ksi, sfmmut, sf_tsbsz_64k);
        SAVE_INT32(ksi, sfmmut, sf_tsbsz_128k);
        SAVE_INT32(ksi, sfmmut, sf_tsbsz_256k);
        SAVE_INT32(ksi, sfmmut, sf_tsbsz_512k);
        SAVE_INT32(ksi, sfmmut, sf_tsbsz_1m);
        SAVE_INT32(ksi, sfmmut, sf_tsbsz_2m);
        SAVE_INT32(ksi, sfmmut, sf_tsbsz_4m);
}
#endif

static void
save_named(kstat_t *kp, ks_instance_t *ksi)
{
        kstat_named_t *knp;
        int     n;

        for (n = kp->ks_ndata, knp = KSTAT_NAMED_PTR(kp); n > 0; n--, knp++) {
                /*
                 * Annoyingly, some drivers have kstats with uninitialized
                 * members (which kstat_install(9F) is sadly powerless to
                 * prevent, and kstat_read(3KSTAT) unfortunately does nothing
                 * to stop).  To prevent these from confusing us to be
                 * KSTAT_DATA_CHAR statistics, we skip over them.
                 */
                if (knp->name[0] == '\0')
                        continue;

                switch (knp->data_type) {
                case KSTAT_DATA_CHAR:
                        nvpair_insert(ksi, knp->name,
                            (ks_value_t *)&knp->value, KSTAT_DATA_CHAR);
                        break;
                case KSTAT_DATA_INT32:
                        nvpair_insert(ksi, knp->name,
                            (ks_value_t *)&knp->value, KSTAT_DATA_INT32);
                        break;
                case KSTAT_DATA_UINT32:
                        nvpair_insert(ksi, knp->name,
                            (ks_value_t *)&knp->value, KSTAT_DATA_UINT32);
                        break;
                case KSTAT_DATA_INT64:
                        nvpair_insert(ksi, knp->name,
                            (ks_value_t *)&knp->value, KSTAT_DATA_INT64);
                        break;
                case KSTAT_DATA_UINT64:
                        nvpair_insert(ksi, knp->name,
                            (ks_value_t *)&knp->value, KSTAT_DATA_UINT64);
                        break;
                case KSTAT_DATA_STRING:
                        SAVE_STRING_X(ksi, knp->name, KSTAT_NAMED_STR_PTR(knp));
                        break;
                default:
                        assert(B_FALSE); /* Invalid data type */
                        break;
                }
        }
}

static void
save_intr(kstat_t *kp, ks_instance_t *ksi)
{
        kstat_intr_t *intr = KSTAT_INTR_PTR(kp);
        char    *intr_names[] = {"hard", "soft", "watchdog", "spurious",
            "multiple_service"};
        int     n;

        for (n = 0; n < KSTAT_NUM_INTRS; n++)
                SAVE_UINT32_X(ksi, intr_names[n], intr->intrs[n]);
}

static void
save_io(kstat_t *kp, ks_instance_t *ksi)
{
        kstat_io_t      *ksio = KSTAT_IO_PTR(kp);

        SAVE_UINT64(ksi, ksio, nread);
        SAVE_UINT64(ksi, ksio, nwritten);
        SAVE_UINT32(ksi, ksio, reads);
        SAVE_UINT32(ksi, ksio, writes);
        SAVE_HRTIME(ksi, ksio, wtime);
        SAVE_HRTIME(ksi, ksio, wlentime);
        SAVE_HRTIME(ksi, ksio, wlastupdate);
        SAVE_HRTIME(ksi, ksio, rtime);
        SAVE_HRTIME(ksi, ksio, rlentime);
        SAVE_HRTIME(ksi, ksio, rlastupdate);
        SAVE_UINT32(ksi, ksio, wcnt);
        SAVE_UINT32(ksi, ksio, rcnt);
}

static void
save_timer(kstat_t *kp, ks_instance_t *ksi)
{
        kstat_timer_t   *ktimer = KSTAT_TIMER_PTR(kp);

        SAVE_STRING(ksi, ktimer, name);
        SAVE_UINT64(ksi, ktimer, num_events);
        SAVE_HRTIME(ksi, ktimer, elapsed_time);
        SAVE_HRTIME(ksi, ktimer, min_time);
        SAVE_HRTIME(ksi, ktimer, max_time);
        SAVE_HRTIME(ksi, ktimer, start_time);
        SAVE_HRTIME(ksi, ktimer, stop_time);
}