summaryrefslogtreecommitdiff
path: root/usr/src/cmd/stat
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/stat
downloadillumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/stat')
-rw-r--r--usr/src/cmd/stat/Makefile49
-rw-r--r--usr/src/cmd/stat/Makefile.stat34
-rw-r--r--usr/src/cmd/stat/common/acquire.c540
-rw-r--r--usr/src/cmd/stat/common/acquire_iodevs.c727
-rw-r--r--usr/src/cmd/stat/common/dsr.c968
-rw-r--r--usr/src/cmd/stat/common/dsr.h142
-rw-r--r--usr/src/cmd/stat/common/mnt.c161
-rw-r--r--usr/src/cmd/stat/common/statcommon.h306
-rw-r--r--usr/src/cmd/stat/common/walkers.c394
-rw-r--r--usr/src/cmd/stat/iostat/Makefile62
-rw-r--r--usr/src/cmd/stat/iostat/iostat.c1769
-rw-r--r--usr/src/cmd/stat/mpstat/Makefile62
-rw-r--r--usr/src/cmd/stat/mpstat/mpstat.c488
-rw-r--r--usr/src/cmd/stat/vmstat/Makefile62
-rw-r--r--usr/src/cmd/stat/vmstat/vmstat.c527
15 files changed, 6291 insertions, 0 deletions
diff --git a/usr/src/cmd/stat/Makefile b/usr/src/cmd/stat/Makefile
new file mode 100644
index 0000000000..0bffef1465
--- /dev/null
+++ b/usr/src/cmd/stat/Makefile
@@ -0,0 +1,49 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (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 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+# cmd/stat/Makefile
+#
+
+include ../Makefile.cmd
+
+SUBDIRS= iostat mpstat vmstat
+
+all := TARGET = all
+install := TARGET = install
+clean := TARGET = clean
+clobber := TARGET = clobber
+lint := TARGET = lint
+_msg := TARGET = _msg
+
+.KEEP_STATE:
+
+all install lint clean clobber _msg: $(SUBDIRS)
+
+$(SUBDIRS): FRC
+ @cd $@; pwd; $(MAKE) $(MFLAGS) $(TARGET)
+
+FRC:
diff --git a/usr/src/cmd/stat/Makefile.stat b/usr/src/cmd/stat/Makefile.stat
new file mode 100644
index 0000000000..720e5e9416
--- /dev/null
+++ b/usr/src/cmd/stat/Makefile.stat
@@ -0,0 +1,34 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (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 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+# cmd/stat/Makefile.stat
+
+STATSRC = $(SRC)/cmd/stat
+STATCOMMONDIR = $(STATSRC)/common
+
+COMMON_OBJS = acquire.o walkers.o acquire_iodevs.o dsr.o mnt.o
+COMMON_SRCS = $(COMMON_OBJS:%.o=$(STATCOMMONDIR)/%.c)
diff --git a/usr/src/cmd/stat/common/acquire.c b/usr/src/cmd/stat/common/acquire.c
new file mode 100644
index 0000000000..e3c911283c
--- /dev/null
+++ b/usr/src/cmd/stat/common/acquire.c
@@ -0,0 +1,540 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "statcommon.h"
+#include "dsr.h"
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <strings.h>
+#include <errno.h>
+#include <poll.h>
+
+#define ARRAY_SIZE(a) (sizeof (a) / sizeof (*a))
+
+/*
+ * The time we delay before retrying after an allocation
+ * failure, in milliseconds
+ */
+#define RETRY_DELAY 200
+
+static char *cpu_states[] = {
+ "cpu_ticks_idle",
+ "cpu_ticks_user",
+ "cpu_ticks_kernel",
+ "cpu_ticks_wait"
+};
+
+extern char cmdname[];
+
+static kstat_t *
+kstat_lookup_read(kstat_ctl_t *kc, char *module,
+ int instance, char *name)
+{
+ kstat_t *ksp = kstat_lookup(kc, module, instance, name);
+ if (ksp == NULL)
+ return (NULL);
+ if (kstat_read(kc, ksp, NULL) == -1)
+ return (NULL);
+ return (ksp);
+}
+
+/*
+ * Note: the following helpers do not clean up on the failure case,
+ * because it is left to the free_snapshot() in the acquire_snapshot()
+ * failure path.
+ */
+
+static int
+acquire_cpus(struct snapshot *ss, kstat_ctl_t *kc)
+{
+ size_t i;
+
+ ss->s_nr_cpus = sysconf(_SC_CPUID_MAX) + 1;
+ ss->s_cpus = calloc(ss->s_nr_cpus, sizeof (struct cpu_snapshot));
+ if (ss->s_cpus == NULL)
+ goto out;
+
+ for (i = 0; i < ss->s_nr_cpus; i++) {
+ kstat_t *ksp;
+
+ ss->s_cpus[i].cs_id = ID_NO_CPU;
+ ss->s_cpus[i].cs_state = p_online(i, P_STATUS);
+ /* If no valid CPU is present, move on to the next one */
+ if (ss->s_cpus[i].cs_state == -1)
+ continue;
+ ss->s_cpus[i].cs_id = i;
+
+ if ((ksp = kstat_lookup_read(kc, "cpu_info", i, NULL)) == NULL)
+ goto out;
+
+ (void) pset_assign(PS_QUERY, i, &ss->s_cpus[i].cs_pset_id);
+ if (ss->s_cpus[i].cs_pset_id == PS_NONE)
+ ss->s_cpus[i].cs_pset_id = ID_NO_PSET;
+
+ if (!CPU_ACTIVE(&ss->s_cpus[i]))
+ continue;
+
+ if ((ksp = kstat_lookup_read(kc, "cpu", i, "vm")) == NULL)
+ goto out;
+
+ if (kstat_copy(ksp, &ss->s_cpus[i].cs_vm))
+ goto out;
+
+ if ((ksp = kstat_lookup_read(kc, "cpu", i, "sys")) == NULL)
+ goto out;
+
+ if (kstat_copy(ksp, &ss->s_cpus[i].cs_sys))
+ goto out;
+ }
+
+ errno = 0;
+out:
+ return (errno);
+}
+
+static int
+acquire_psets(struct snapshot *ss)
+{
+ psetid_t *pids = NULL;
+ struct pset_snapshot *ps;
+ size_t pids_nr;
+ size_t i, j;
+
+ /*
+ * Careful in this code. We have to use pset_list
+ * twice, but inbetween pids_nr can change at will.
+ * We delay the setting of s_nr_psets until we have
+ * the "final" value of pids_nr.
+ */
+
+ if (pset_list(NULL, &pids_nr) < 0)
+ return (errno);
+
+ if ((pids = calloc(pids_nr, sizeof (psetid_t))) == NULL)
+ goto out;
+
+ if (pset_list(pids, &pids_nr) < 0)
+ goto out;
+
+ ss->s_psets = calloc(pids_nr + 1, sizeof (struct pset_snapshot));
+ if (ss->s_psets == NULL)
+ goto out;
+ ss->s_nr_psets = pids_nr + 1;
+
+ /* CPUs not in any actual pset */
+ ps = &ss->s_psets[0];
+ ps->ps_id = 0;
+ ps->ps_cpus = calloc(ss->s_nr_cpus, sizeof (struct cpu_snapshot *));
+ if (ps->ps_cpus == NULL)
+ goto out;
+
+ /* CPUs in a a pset */
+ for (i = 1; i < ss->s_nr_psets; i++) {
+ ps = &ss->s_psets[i];
+
+ ps->ps_id = pids[i - 1];
+ ps->ps_cpus =
+ calloc(ss->s_nr_cpus, sizeof (struct cpu_snapshot *));
+ if (ps->ps_cpus == NULL)
+ goto out;
+ }
+
+ for (i = 0; i < ss->s_nr_psets; i++) {
+ ps = &ss->s_psets[i];
+
+ for (j = 0; j < ss->s_nr_cpus; j++) {
+ if (!CPU_ACTIVE(&ss->s_cpus[j]))
+ continue;
+ if (ss->s_cpus[j].cs_pset_id != ps->ps_id)
+ continue;
+
+ ps->ps_cpus[ps->ps_nr_cpus++] = &ss->s_cpus[j];
+ }
+ }
+
+ errno = 0;
+out:
+ free(pids);
+ return (errno);
+}
+
+static int
+acquire_intrs(struct snapshot *ss, kstat_ctl_t *kc)
+{
+ kstat_t *ksp;
+ size_t i = 0;
+ kstat_t *sys_misc;
+ kstat_named_t *clock;
+
+ /* clock interrupt */
+ ss->s_nr_intrs = 1;
+
+ for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
+ if (ksp->ks_type == KSTAT_TYPE_INTR)
+ ss->s_nr_intrs++;
+ }
+
+ ss->s_intrs = calloc(ss->s_nr_intrs, sizeof (struct intr_snapshot));
+ if (ss->s_intrs == NULL)
+ return (errno);
+
+ sys_misc = kstat_lookup_read(kc, "unix", 0, "system_misc");
+ if (sys_misc == NULL)
+ goto out;
+
+ clock = (kstat_named_t *)kstat_data_lookup(sys_misc, "clk_intr");
+ if (clock == NULL)
+ goto out;
+
+ (void) strlcpy(ss->s_intrs[0].is_name, "clock", KSTAT_STRLEN);
+ ss->s_intrs[0].is_total = clock->value.ui32;
+
+ i = 1;
+
+ for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
+ kstat_intr_t *ki;
+ int j;
+
+ if (ksp->ks_type != KSTAT_TYPE_INTR)
+ continue;
+ if (kstat_read(kc, ksp, NULL) == -1)
+ goto out;
+
+ ki = KSTAT_INTR_PTR(ksp);
+
+ (void) strlcpy(ss->s_intrs[i].is_name, ksp->ks_name,
+ KSTAT_STRLEN);
+ ss->s_intrs[i].is_total = 0;
+
+ for (j = 0; j < KSTAT_NUM_INTRS; j++)
+ ss->s_intrs[i].is_total += ki->intrs[j];
+
+ i++;
+ }
+
+ errno = 0;
+out:
+ return (errno);
+}
+
+int
+acquire_sys(struct snapshot *ss, kstat_ctl_t *kc)
+{
+ size_t i;
+ kstat_named_t *knp;
+ kstat_t *ksp;
+
+ if ((ksp = kstat_lookup(kc, "unix", 0, "sysinfo")) == NULL)
+ return (errno);
+
+ if (kstat_read(kc, ksp, &ss->s_sys.ss_sysinfo) == -1)
+ return (errno);
+
+ if ((ksp = kstat_lookup(kc, "unix", 0, "vminfo")) == NULL)
+ return (errno);
+
+ if (kstat_read(kc, ksp, &ss->s_sys.ss_vminfo) == -1)
+ return (errno);
+
+ if ((ksp = kstat_lookup(kc, "unix", 0, "dnlcstats")) == NULL)
+ return (errno);
+
+ if (kstat_read(kc, ksp, &ss->s_sys.ss_nc) == -1)
+ return (errno);
+
+ if ((ksp = kstat_lookup(kc, "unix", 0, "system_misc")) == NULL)
+ return (errno);
+
+ if (kstat_read(kc, ksp, NULL) == -1)
+ return (errno);
+
+ knp = (kstat_named_t *)kstat_data_lookup(ksp, "clk_intr");
+ if (knp == NULL)
+ return (errno);
+
+ ss->s_sys.ss_ticks = knp->value.l;
+
+ knp = (kstat_named_t *)kstat_data_lookup(ksp, "deficit");
+ if (knp == NULL)
+ return (errno);
+
+ ss->s_sys.ss_deficit = knp->value.l;
+
+ for (i = 0; i < ss->s_nr_cpus; i++) {
+ if (!CPU_ACTIVE(&ss->s_cpus[i]))
+ continue;
+
+ if (kstat_add(&ss->s_cpus[i].cs_sys, &ss->s_sys.ss_agg_sys))
+ return (errno);
+ if (kstat_add(&ss->s_cpus[i].cs_vm, &ss->s_sys.ss_agg_vm))
+ return (errno);
+ }
+
+ return (0);
+}
+
+struct snapshot *
+acquire_snapshot(kstat_ctl_t *kc, int types, struct iodev_filter *iodev_filter)
+{
+ struct snapshot *ss = NULL;
+ int err;
+
+retry:
+ err = 0;
+ /* ensure any partial resources are freed on a retry */
+ free_snapshot(ss);
+
+ ss = safe_alloc(sizeof (struct snapshot));
+
+ (void) memset(ss, 0, sizeof (struct snapshot));
+
+ ss->s_types = types;
+
+ /* wait for a possibly up-to-date chain */
+ while (kstat_chain_update(kc) == -1) {
+ if (errno == EAGAIN)
+ (void) poll(NULL, 0, RETRY_DELAY);
+ else
+ fail(1, "kstat_chain_update failed");
+ }
+
+ if (types & SNAP_FLUSHES) {
+ kstat_t *ksp;
+ ksp = kstat_lookup(kc, "unix", 0, "flushmeter");
+ if (ksp == NULL) {
+ fail(0, "This machine does not have "
+ "a virtual address cache");
+ }
+ if (kstat_read(kc, ksp, &ss->s_flushes) == -1)
+ err = errno;
+ }
+
+ if (!err && (types & SNAP_INTERRUPTS))
+ err = acquire_intrs(ss, kc);
+
+ if (!err && (types & (SNAP_CPUS | SNAP_SYSTEM | SNAP_PSETS)))
+ err = acquire_cpus(ss, kc);
+
+ if (!err && (types & SNAP_PSETS))
+ err = acquire_psets(ss);
+
+ if (!err && (types & (SNAP_IODEVS | SNAP_CONTROLLERS | SNAP_IOPATHS)))
+ err = acquire_iodevs(ss, kc, iodev_filter);
+
+ if (!err && (types & SNAP_SYSTEM))
+ err = acquire_sys(ss, kc);
+
+ switch (err) {
+ case 0:
+ break;
+ case EAGAIN:
+ (void) poll(NULL, 0, RETRY_DELAY);
+ /* a kstat disappeared from under us */
+ /*FALLTHRU*/
+ case ENXIO:
+ case ENOENT:
+ goto retry;
+ default:
+ fail(1, "acquiring snapshot failed");
+ }
+
+ return (ss);
+}
+
+void
+free_snapshot(struct snapshot *ss)
+{
+ size_t i;
+
+ if (ss == NULL)
+ return;
+
+ while (ss->s_iodevs) {
+ struct iodev_snapshot *tmp = ss->s_iodevs;
+ ss->s_iodevs = ss->s_iodevs->is_next;
+ free_iodev(tmp);
+ }
+
+ if (ss->s_cpus) {
+ for (i = 0; i < ss->s_nr_cpus; i++) {
+ free(ss->s_cpus[i].cs_vm.ks_data);
+ free(ss->s_cpus[i].cs_sys.ks_data);
+ }
+ free(ss->s_cpus);
+ }
+
+ if (ss->s_psets) {
+ for (i = 0; i < ss->s_nr_psets; i++)
+ free(ss->s_psets[i].ps_cpus);
+ free(ss->s_psets);
+ }
+
+ free(ss->s_sys.ss_agg_sys.ks_data);
+ free(ss->s_sys.ss_agg_vm.ks_data);
+ free(ss);
+}
+
+kstat_ctl_t *
+open_kstat(void)
+{
+ kstat_ctl_t *kc;
+
+ while ((kc = kstat_open()) == NULL) {
+ if (errno == EAGAIN)
+ (void) poll(NULL, 0, RETRY_DELAY);
+ else
+ fail(1, "kstat_open failed");
+ }
+
+ return (kc);
+}
+
+/*PRINTFLIKE2*/
+void
+fail(int do_perror, char *message, ...)
+{
+ va_list args;
+ int save_errno = errno;
+
+ va_start(args, message);
+ (void) fprintf(stderr, "%s: ", cmdname);
+ (void) vfprintf(stderr, message, args);
+ va_end(args);
+ if (do_perror)
+ (void) fprintf(stderr, ": %s", strerror(save_errno));
+ (void) fprintf(stderr, "\n");
+ exit(2);
+}
+
+void *
+safe_alloc(size_t size)
+{
+ void *ptr;
+
+ while ((ptr = malloc(size)) == NULL) {
+ if (errno == EAGAIN)
+ (void) poll(NULL, 0, RETRY_DELAY);
+ else
+ fail(1, "malloc failed");
+ }
+ return (ptr);
+}
+
+char *
+safe_strdup(char *str)
+{
+ char *ret;
+
+ if (str == NULL)
+ return (NULL);
+
+ while ((ret = strdup(str)) == NULL) {
+ if (errno == EAGAIN)
+ (void) poll(NULL, 0, RETRY_DELAY);
+ else
+ fail(1, "malloc failed");
+ }
+ return (ret);
+}
+
+uint64_t
+kstat_delta(kstat_t *old, kstat_t *new, char *name)
+{
+ kstat_named_t *knew = kstat_data_lookup(new, name);
+ if (old && old->ks_data) {
+ kstat_named_t *kold = kstat_data_lookup(old, name);
+ return (knew->value.ui64 - kold->value.ui64);
+ }
+ return (knew->value.ui64);
+}
+
+int
+kstat_copy(const kstat_t *src, kstat_t *dst)
+{
+ *dst = *src;
+
+ if (src->ks_data != NULL) {
+ if ((dst->ks_data = malloc(src->ks_data_size)) == NULL)
+ return (-1);
+ bcopy(src->ks_data, dst->ks_data, src->ks_data_size);
+ } else {
+ dst->ks_data = NULL;
+ dst->ks_data_size = 0;
+ }
+ return (0);
+}
+
+int
+kstat_add(const kstat_t *src, kstat_t *dst)
+{
+ size_t i;
+ kstat_named_t *from;
+ kstat_named_t *to;
+
+ if (dst->ks_data == NULL)
+ return (kstat_copy(src, dst));
+
+ from = src->ks_data;
+ to = dst->ks_data;
+
+ for (i = 0; i < src->ks_ndata; i++) {
+ /* "addition" makes little sense for strings */
+ if (from->data_type != KSTAT_DATA_CHAR &&
+ from->data_type != KSTAT_DATA_STRING)
+ (to)->value.ui64 += (from)->value.ui64;
+ from++;
+ to++;
+ }
+
+ return (0);
+}
+
+uint64_t
+cpu_ticks_delta(kstat_t *old, kstat_t *new)
+{
+ uint64_t ticks = 0;
+ size_t i;
+ for (i = 0; i < ARRAY_SIZE(cpu_states); i++)
+ ticks += kstat_delta(old, new, cpu_states[i]);
+ return (ticks);
+}
+
+int
+nr_active_cpus(struct snapshot *ss)
+{
+ size_t i;
+ int count = 0;
+ for (i = 0; i < ss->s_nr_cpus; i++) {
+ if (CPU_ACTIVE(&ss->s_cpus[i]))
+ count++;
+ }
+
+ return (count);
+}
diff --git a/usr/src/cmd/stat/common/acquire_iodevs.c b/usr/src/cmd/stat/common/acquire_iodevs.c
new file mode 100644
index 0000000000..cd7d167c97
--- /dev/null
+++ b/usr/src/cmd/stat/common/acquire_iodevs.c
@@ -0,0 +1,727 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "statcommon.h"
+#include "dsr.h"
+
+#include <sys/dklabel.h>
+#include <sys/dktp/fdisk.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <strings.h>
+#include <errno.h>
+#include <limits.h>
+
+static void insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev);
+
+static struct iodev_snapshot *
+make_controller(int cid)
+{
+ struct iodev_snapshot *new;
+
+ new = safe_alloc(sizeof (struct iodev_snapshot));
+ (void) memset(new, 0, sizeof (struct iodev_snapshot));
+ new->is_type = IODEV_CONTROLLER;
+ new->is_id.id = cid;
+ new->is_parent_id.id = IODEV_NO_ID;
+
+ (void) snprintf(new->is_name, sizeof (new->is_name), "c%d", cid);
+
+ return (new);
+}
+
+static struct iodev_snapshot *
+find_iodev_by_name(struct iodev_snapshot *list, const char *name)
+{
+ struct iodev_snapshot *pos;
+ struct iodev_snapshot *pos2;
+
+ for (pos = list; pos; pos = pos->is_next) {
+ if (strcmp(pos->is_name, name) == 0)
+ return (pos);
+
+ pos2 = find_iodev_by_name(pos->is_children, name);
+ if (pos2 != NULL)
+ return (pos2);
+ }
+
+ return (NULL);
+}
+
+static enum iodev_type
+parent_iodev_type(enum iodev_type type)
+{
+ switch (type) {
+ case IODEV_CONTROLLER: return (0);
+ case IODEV_NFS: return (0);
+ case IODEV_TAPE: return (0);
+ case IODEV_IOPATH: return (IODEV_DISK);
+ case IODEV_DISK: return (IODEV_CONTROLLER);
+ case IODEV_PARTITION: return (IODEV_DISK);
+ }
+ return (IODEV_UNKNOWN);
+}
+
+static int
+id_match(struct iodev_id *id1, struct iodev_id *id2)
+{
+ return (id1->id == id2->id &&
+ strcmp(id1->tid, id2->tid) == 0);
+}
+
+static struct iodev_snapshot *
+find_parent(struct snapshot *ss, struct iodev_snapshot *iodev)
+{
+ enum iodev_type parent_type = parent_iodev_type(iodev->is_type);
+ struct iodev_snapshot *pos;
+ struct iodev_snapshot *pos2;
+
+ if (parent_type == 0 || parent_type == IODEV_UNKNOWN)
+ return (NULL);
+
+ if (iodev->is_parent_id.id == IODEV_NO_ID &&
+ iodev->is_parent_id.tid[0] == '\0')
+ return (NULL);
+
+ if (parent_type == IODEV_CONTROLLER) {
+ for (pos = ss->s_iodevs; pos; pos = pos->is_next) {
+ if (pos->is_type != IODEV_CONTROLLER)
+ continue;
+ if (pos->is_id.id != iodev->is_parent_id.id)
+ continue;
+ return (pos);
+ }
+
+ if (!(ss->s_types & SNAP_CONTROLLERS))
+ return (NULL);
+
+ pos = make_controller(iodev->is_parent_id.id);
+ insert_iodev(ss, pos);
+ return (pos);
+ }
+
+ /* IODEV_DISK parent */
+ for (pos = ss->s_iodevs; pos; pos = pos->is_next) {
+ if (id_match(&iodev->is_parent_id, &pos->is_id) &&
+ pos->is_type == IODEV_DISK)
+ return (pos);
+ if (pos->is_type != IODEV_CONTROLLER)
+ continue;
+ for (pos2 = pos->is_children; pos2; pos2 = pos2->is_next) {
+ if (pos2->is_type != IODEV_DISK)
+ continue;
+ if (id_match(&iodev->is_parent_id, &pos2->is_id))
+ return (pos2);
+ }
+ }
+
+ return (NULL);
+}
+
+static void
+list_del(struct iodev_snapshot **list, struct iodev_snapshot *pos)
+{
+ if (*list == pos)
+ *list = pos->is_next;
+ if (pos->is_next)
+ pos->is_next->is_prev = pos->is_prev;
+ if (pos->is_prev)
+ pos->is_prev->is_next = pos->is_next;
+ pos->is_prev = pos->is_next = NULL;
+}
+
+static void
+insert_before(struct iodev_snapshot **list, struct iodev_snapshot *pos,
+ struct iodev_snapshot *new)
+{
+ if (pos == NULL) {
+ new->is_prev = new->is_next = NULL;
+ *list = new;
+ return;
+ }
+
+ new->is_next = pos;
+ new->is_prev = pos->is_prev;
+ if (pos->is_prev)
+ pos->is_prev->is_next = new;
+ else
+ *list = new;
+ pos->is_prev = new;
+}
+
+static void
+insert_after(struct iodev_snapshot **list, struct iodev_snapshot *pos,
+ struct iodev_snapshot *new)
+{
+ if (pos == NULL) {
+ new->is_prev = new->is_next = NULL;
+ *list = new;
+ return;
+ }
+
+ new->is_next = pos->is_next;
+ new->is_prev = pos;
+ if (pos->is_next)
+ pos->is_next->is_prev = new;
+ pos->is_next = new;
+}
+
+static void
+insert_into(struct iodev_snapshot **list, struct iodev_snapshot *iodev)
+{
+ struct iodev_snapshot *tmp = *list;
+ if (*list == NULL) {
+ *list = iodev;
+ return;
+ }
+
+ for (;;) {
+ if (iodev_cmp(tmp, iodev) > 0) {
+ insert_before(list, tmp, iodev);
+ return;
+ }
+
+ if (tmp->is_next == NULL)
+ break;
+
+ tmp = tmp->is_next;
+ }
+
+ insert_after(list, tmp, iodev);
+}
+
+static int
+disk_or_partition(enum iodev_type type)
+{
+ return (type == IODEV_DISK || type == IODEV_PARTITION);
+}
+
+static void
+insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev)
+{
+ struct iodev_snapshot *parent = find_parent(ss, iodev);
+ struct iodev_snapshot **list;
+
+ if (parent != NULL) {
+ list = &parent->is_children;
+ parent->is_nr_children++;
+ } else {
+ list = &ss->s_iodevs;
+ ss->s_nr_iodevs++;
+ }
+
+ insert_into(list, iodev);
+}
+
+static int
+iodev_match(struct iodev_snapshot *dev, struct iodev_filter *df)
+{
+ size_t i;
+ int is_floppy = (strncmp(dev->is_name, "fd", 2) == 0);
+
+ /* no filter, pass */
+ if (df == NULL)
+ return (1);
+
+ /* no filtered names, pass if not floppy and skipped */
+ if (df->if_nr_names == NULL)
+ return (!(df->if_skip_floppy && is_floppy));
+
+ for (i = 0; i < df->if_nr_names; i++) {
+ if (strcmp(dev->is_name, df->if_names[i]) == 0)
+ return (1);
+ if (dev->is_pretty != NULL &&
+ strcmp(dev->is_pretty, df->if_names[i]) == 0)
+ return (1);
+ }
+
+ /* not found in specified names, fail match */
+ return (0);
+}
+
+/* select which I/O devices to collect stats for */
+static void
+choose_iodevs(struct snapshot *ss, struct iodev_snapshot *iodevs,
+ struct iodev_filter *df)
+{
+ struct iodev_snapshot *pos = iodevs;
+ int nr_iodevs = df ? df->if_max_iodevs : UNLIMITED_IODEVS;
+
+ if (nr_iodevs == UNLIMITED_IODEVS)
+ nr_iodevs = INT_MAX;
+
+ while (pos && nr_iodevs) {
+ struct iodev_snapshot *tmp = pos;
+ pos = pos->is_next;
+
+ if (!iodev_match(tmp, df))
+ continue;
+
+ list_del(&iodevs, tmp);
+ insert_iodev(ss, tmp);
+
+ --nr_iodevs;
+ }
+
+ pos = iodevs;
+
+ /* now insert any iodevs into the remaining slots */
+ while (pos && nr_iodevs) {
+ struct iodev_snapshot *tmp = pos;
+ pos = pos->is_next;
+
+ if (df && df->if_skip_floppy &&
+ strncmp(tmp->is_name, "fd", 2) == 0)
+ continue;
+
+ list_del(&iodevs, tmp);
+ insert_iodev(ss, tmp);
+
+ --nr_iodevs;
+ }
+
+ /* clear the unwanted ones */
+ pos = iodevs;
+ while (pos) {
+ struct iodev_snapshot *tmp = pos;
+ pos = pos->is_next;
+ free_iodev(tmp);
+ }
+}
+
+static int
+collate_controller(struct iodev_snapshot *controller,
+ struct iodev_snapshot *disk)
+{
+ controller->is_stats.nread += disk->is_stats.nread;
+ controller->is_stats.nwritten += disk->is_stats.nwritten;
+ controller->is_stats.reads += disk->is_stats.reads;
+ controller->is_stats.writes += disk->is_stats.writes;
+ controller->is_stats.wtime += disk->is_stats.wtime;
+ controller->is_stats.wlentime += disk->is_stats.wlentime;
+ controller->is_stats.rtime += disk->is_stats.rtime;
+ controller->is_stats.rlentime += disk->is_stats.rlentime;
+ controller->is_crtime += disk->is_crtime;
+ controller->is_snaptime += disk->is_snaptime;
+ if (kstat_add(&disk->is_errors, &controller->is_errors))
+ return (errno);
+ return (0);
+}
+
+static int
+acquire_iodev_stats(struct iodev_snapshot *list, kstat_ctl_t *kc)
+{
+ struct iodev_snapshot *pos;
+ int err = 0;
+
+ for (pos = list; pos; pos = pos->is_next) {
+ /* controllers don't have stats (yet) */
+ if (pos->is_ksp != NULL) {
+ if (kstat_read(kc, pos->is_ksp, &pos->is_stats) == -1)
+ return (errno);
+ /* make sure crtime/snaptime is updated */
+ pos->is_crtime = pos->is_ksp->ks_crtime;
+ pos->is_snaptime = pos->is_ksp->ks_snaptime;
+ }
+
+ if ((err = acquire_iodev_stats(pos->is_children, kc)))
+ return (err);
+
+ if (pos->is_type == IODEV_CONTROLLER) {
+ struct iodev_snapshot *pos2 = pos->is_children;
+
+ for (; pos2; pos2 = pos2->is_next) {
+ if ((err = collate_controller(pos, pos2)))
+ return (err);
+ }
+ }
+ }
+
+ return (0);
+}
+
+static int
+acquire_iodev_errors(struct snapshot *ss, kstat_ctl_t *kc)
+{
+ kstat_t *ksp;
+
+ if (!(ss->s_types && SNAP_IODEV_ERRORS))
+ return (0);
+
+ for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
+ char kstat_name[KSTAT_STRLEN];
+ char *dname = kstat_name;
+ char *ename = ksp->ks_name;
+ struct iodev_snapshot *iodev;
+
+ if (ksp->ks_type != KSTAT_TYPE_NAMED)
+ continue;
+ if (strncmp(ksp->ks_class, "device_error", 12) != 0 &&
+ strncmp(ksp->ks_class, "iopath_error", 12) != 0)
+ continue;
+
+ /*
+ * Some drivers may not follow the naming convention
+ * for error kstats (i.e., drivername,err) so
+ * be sure we don't walk off the end.
+ */
+ while (*ename && *ename != ',') {
+ *dname = *ename;
+ dname++;
+ ename++;
+ }
+ *dname = '\0';
+
+ iodev = find_iodev_by_name(ss->s_iodevs, kstat_name);
+
+ if (iodev == NULL)
+ continue;
+
+ if (kstat_read(kc, ksp, NULL) == -1)
+ return (errno);
+ if (kstat_copy(ksp, &iodev->is_errors) == -1)
+ return (errno);
+ }
+
+ return (0);
+}
+
+static void
+get_ids(struct iodev_snapshot *iodev, const char *pretty)
+{
+ int ctr, disk, slice, ret;
+ char *target;
+ const char *p1;
+ const char *p2;
+
+ if (pretty == NULL)
+ return;
+
+ if (sscanf(pretty, "c%d", &ctr) != 1)
+ return;
+
+ p1 = pretty;
+ while (*p1 && *p1 != 't')
+ ++p1;
+
+ if (!*p1)
+ return;
+ ++p1;
+
+ p2 = p1;
+ while (*p2 && *p2 != 'd')
+ ++p2;
+
+ if (!*p2 || p2 == p1)
+ return;
+
+ target = safe_alloc(1 + p2 - p1);
+ (void) strlcpy(target, p1, 1 + p2 - p1);
+
+ ret = sscanf(p2, "d%d%*[sp]%d", &disk, &slice);
+
+ if (ret == 2 && iodev->is_type == IODEV_PARTITION) {
+ iodev->is_id.id = slice;
+ iodev->is_parent_id.id = disk;
+ (void) strlcpy(iodev->is_parent_id.tid, target, KSTAT_STRLEN);
+ } else if (ret == 1) {
+ if (iodev->is_type == IODEV_DISK) {
+ iodev->is_id.id = disk;
+ (void) strlcpy(iodev->is_id.tid, target, KSTAT_STRLEN);
+ iodev->is_parent_id.id = ctr;
+ } else if (iodev->is_type == IODEV_IOPATH) {
+ iodev->is_parent_id.id = disk;
+ (void) strlcpy(iodev->is_parent_id.tid,
+ target, KSTAT_STRLEN);
+ }
+ }
+
+ free(target);
+}
+
+static char *
+get_slice(int partition, disk_list_t *dl)
+{
+ char *tmpbuf;
+ size_t tmplen;
+
+ if (!(dl->flags & SLICES_OK))
+ return (NULL);
+ if (partition < 0 || partition >= NDKMAP)
+ return (NULL);
+
+ /* space for 's', and integer < NDKMAP (16) */
+ tmplen = strlen(dl->dsk) + strlen("sXX") + 1;
+ tmpbuf = safe_alloc(tmplen);
+
+ /*
+ * This is a regular slice. Create the name and
+ * copy it for use by the calling routine.
+ */
+ (void) snprintf(tmpbuf, tmplen, "%ss%d", dl->dsk, partition);
+ return (tmpbuf);
+}
+
+static char *
+get_intel_partition(int partition, disk_list_t *dl)
+{
+ char *tmpbuf;
+ size_t tmplen;
+
+ if (partition <= 0 || !(dl->flags & PARTITIONS_OK))
+ return (NULL);
+
+ /*
+ * See if it falls in the range of allowable partitions. The
+ * fdisk partitions show up after the traditional slices so we
+ * determine which partition we're in and return that.
+ * The NUMPART + 1 is not a mistake. There are currently
+ * FD_NUMPART + 1 partitions that show up in the device directory.
+ */
+ partition -= NDKMAP;
+ if (partition < 0 || partition >= (FD_NUMPART + 1))
+ return (NULL);
+
+ /* space for 'p', and integer < NDKMAP (16) */
+ tmplen = strlen(dl->dsk) + strlen("pXX") + 1;
+ tmpbuf = safe_alloc(tmplen);
+
+ (void) snprintf(tmpbuf, tmplen, "%sp%d", dl->dsk, partition);
+ return (tmpbuf);
+}
+
+static void
+get_pretty_name(enum snapshot_types types, struct iodev_snapshot *iodev,
+ kstat_ctl_t *kc)
+{
+ disk_list_t *dl;
+ char *pretty = NULL;
+ char *tmp;
+ int partition;
+
+ if (iodev->is_type == IODEV_NFS) {
+ if (!(types & SNAP_IODEV_PRETTY))
+ return;
+
+ iodev->is_pretty = lookup_nfs_name(iodev->is_name, kc);
+ return;
+ }
+
+ if (iodev->is_type == IODEV_IOPATH) {
+ char buf[KSTAT_STRLEN];
+ size_t len;
+
+ tmp = iodev->is_name;
+ while (*tmp && *tmp != '.')
+ tmp++;
+ if (!*tmp)
+ return;
+ (void) strlcpy(buf, iodev->is_name, 1 + tmp - iodev->is_name);
+ dl = lookup_ks_name(buf);
+ if (dl == NULL || dl->dsk == NULL)
+ return;
+ len = strlen(dl->dsk) + strlen(tmp) + 1;
+ pretty = safe_alloc(len);
+ (void) strlcpy(pretty, dl->dsk, len);
+ (void) strlcat(pretty, tmp, len);
+ goto out;
+ }
+
+ dl = lookup_ks_name(iodev->is_name);
+ if (dl == NULL)
+ return;
+
+ if (dl->dsk)
+ pretty = safe_strdup(dl->dsk);
+
+ if (types & SNAP_IODEV_PRETTY) {
+ if (dl->dname)
+ iodev->is_dname = safe_strdup(dl->dname);
+ }
+
+ if (dl->devidstr)
+ iodev->is_devid = safe_strdup(dl->devidstr);
+
+ /* look for a possible partition number */
+ tmp = iodev->is_name;
+ while (*tmp && *tmp != ',')
+ tmp++;
+ if (*tmp != ',')
+ goto out;
+
+ tmp++;
+ partition = (int)(*tmp - 'a');
+
+ if (iodev->is_type == IODEV_PARTITION) {
+ char *part;
+ if ((part = get_slice(partition, dl)) == NULL)
+ part = get_intel_partition(partition, dl);
+ if (part != NULL) {
+ free(pretty);
+ pretty = part;
+ }
+ }
+
+out:
+ get_ids(iodev, pretty);
+
+ /* only fill in the pretty name if specifically asked for */
+ if (types & SNAP_IODEV_PRETTY) {
+ iodev->is_pretty = pretty;
+ } else {
+ free(pretty);
+ }
+}
+
+static enum iodev_type
+get_iodev_type(kstat_t *ksp)
+{
+ if (strcmp(ksp->ks_class, "disk") == 0)
+ return (IODEV_DISK);
+ if (strcmp(ksp->ks_class, "partition") == 0)
+ return (IODEV_PARTITION);
+ if (strcmp(ksp->ks_class, "nfs") == 0)
+ return (IODEV_NFS);
+ if (strcmp(ksp->ks_class, "iopath") == 0)
+ return (IODEV_IOPATH);
+ if (strcmp(ksp->ks_class, "tape") == 0)
+ return (IODEV_TAPE);
+ return (IODEV_UNKNOWN);
+}
+
+int
+iodev_cmp(struct iodev_snapshot *io1, struct iodev_snapshot *io2)
+{
+ /* neutral sort order between disk and part */
+ if (!disk_or_partition(io1->is_type) ||
+ !disk_or_partition(io2->is_type)) {
+ if (io1->is_type < io2->is_type)
+ return (-1);
+ if (io1->is_type > io2->is_type)
+ return (1);
+ }
+
+ /* controller doesn't have ksp */
+ if (io1->is_ksp && io2->is_ksp) {
+ if (strcmp(io1->is_module, io2->is_module) != 0)
+ return (strcmp(io1->is_module, io2->is_module));
+ if (io1->is_instance < io2->is_instance)
+ return (-1);
+ if (io1->is_instance > io2->is_instance)
+ return (1);
+ } else {
+ if (io1->is_id.id < io2->is_id.id)
+ return (-1);
+ if (io1->is_id.id > io2->is_id.id)
+ return (1);
+ }
+
+ return (strcmp(io1->is_name, io2->is_name));
+}
+
+int
+acquire_iodevs(struct snapshot *ss, kstat_ctl_t *kc, struct iodev_filter *df)
+{
+ kstat_t *ksp;
+ int err = 0;
+ struct iodev_snapshot *pos;
+ struct iodev_snapshot *list = NULL;
+
+ ss->s_nr_iodevs = 0;
+
+ for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
+ enum iodev_type type;
+
+ if (ksp->ks_type != KSTAT_TYPE_IO)
+ continue;
+
+ /* e.g. "usb_byte_count" is not handled */
+ if ((type = get_iodev_type(ksp)) == IODEV_UNKNOWN)
+ continue;
+
+ if (df && !(type & df->if_allowed_types))
+ continue;
+
+ if ((pos = malloc(sizeof (struct iodev_snapshot))) == NULL) {
+ err = errno;
+ goto out;
+ }
+
+ (void) memset(pos, 0, sizeof (struct iodev_snapshot));
+
+ pos->is_type = type;
+ pos->is_crtime = ksp->ks_crtime;
+ pos->is_snaptime = ksp->ks_snaptime;
+ pos->is_id.id = IODEV_NO_ID;
+ pos->is_parent_id.id = IODEV_NO_ID;
+ pos->is_ksp = ksp;
+ pos->is_instance = ksp->ks_instance;
+
+ (void) strlcpy(pos->is_module, ksp->ks_module, KSTAT_STRLEN);
+ (void) strlcpy(pos->is_name, ksp->ks_name, KSTAT_STRLEN);
+ get_pretty_name(ss->s_types, pos, kc);
+
+ /*
+ * We must insert in sort order so e.g. vmstat -l
+ * chooses in order.
+ */
+ insert_into(&list, pos);
+ }
+
+ choose_iodevs(ss, list, df);
+
+ /* before acquire_stats for collate_controller()'s benefit */
+ if (ss->s_types & SNAP_IODEV_ERRORS) {
+ if ((err = acquire_iodev_errors(ss, kc)) != 0)
+ goto out;
+ }
+
+ if ((err = acquire_iodev_stats(ss->s_iodevs, kc)) != 0)
+ goto out;
+
+ err = 0;
+out:
+ return (err);
+}
+
+void
+free_iodev(struct iodev_snapshot *iodev)
+{
+ while (iodev->is_children) {
+ struct iodev_snapshot *tmp = iodev->is_children;
+ iodev->is_children = iodev->is_children->is_next;
+ free_iodev(tmp);
+ }
+
+ free(iodev->is_errors.ks_data);
+ free(iodev->is_pretty);
+ free(iodev->is_dname);
+ free(iodev->is_devid);
+ free(iodev);
+}
diff --git a/usr/src/cmd/stat/common/dsr.c b/usr/src/cmd/stat/common/dsr.c
new file mode 100644
index 0000000000..bbbfaeec3e
--- /dev/null
+++ b/usr/src/cmd/stat/common/dsr.c
@@ -0,0 +1,968 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+/*
+ * Dependent on types.h, but not including it...
+ */
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/dkio.h>
+#include <sys/dktp/fdisk.h>
+#include <sys/mnttab.h>
+#include <sys/mntent.h>
+#include <sys/sysmacros.h>
+#include <sys/mkdev.h>
+#include <sys/vfs.h>
+#include <nfs/nfs.h>
+#include <nfs/nfs_clnt.h>
+#include <kstat.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <libdevinfo.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <devid.h>
+
+#include "dsr.h"
+#include "statcommon.h"
+
+static void rummage_dev(ldinfo_t *);
+static void do_snm(char *, char *);
+static int look_up_name(const char *, disk_list_t *);
+static disk_list_t *make_an_entry(char *, char *,
+ char *, dir_info_t *, int, ldinfo_t *);
+static char *trim(char *, char *, int);
+static ldinfo_t *rummage_devinfo(void);
+static void pline(char *, int, char *, char *, ldinfo_t **);
+static void insert_dlist_ent(disk_list_t *, disk_list_t **);
+static int str_is_digit(char *);
+static ldinfo_t *find_ldinfo_match(char *, ldinfo_t *);
+
+static void insert_into_dlist(dir_info_t *, disk_list_t *);
+static void cleanup_dlist(dir_info_t *);
+static void cleanup_ldinfo(ldinfo_t *);
+static int devinfo_ident_disks(di_node_t, void *);
+static int devinfo_ident_tapes(di_node_t, void *);
+static void process_dir_ent(char *dent, int curr_type,
+ char *last_snm, dir_info_t *, ldinfo_t *);
+
+static char *get_nfs_by_minor(uint_t);
+static char *cur_hostname(uint_t, kstat_ctl_t *);
+static char *cur_special(char *, char *);
+
+extern kstat_ctl_t *kc;
+extern mnt_t *nfs;
+
+/*
+ * To do: add VXVM support: /dev/vx/dsk and ap support: /dev/ap/
+ *
+ * Note: Adding support for VxVM is *not* as simple as adding another
+ * entry in the table and magically getting to see stuff related to
+ * VxVM. The structure is radically different *AND* they don't produce
+ * any IO kstats.
+ */
+
+#define OSA_DISK 0
+#define DISK 1
+#define MD_DISK 2
+#define TAPE 3
+
+#define MAX_TYPES 4
+
+#define OSA_DISK_PATH "/dev/osa/dev/dsk"
+#define MD_DISK_PATH "/dev/md/dsk"
+#define DISK_PATH "/dev/dsk"
+#define TAPE_PATH "/dev/rmt"
+
+#define BASE_TRIM "../../devices"
+#define MD_TRIM "../../../devices"
+#define COLON ':'
+#define COMMA ','
+
+#define NAME_BUFLEN 256
+
+static dir_info_t dlist[MAX_TYPES] = {
+ OSA_DISK_PATH, 0, 0, 0, 0, "sd", BASE_TRIM, COLON,
+ DISK_PATH, 0, 0, 0, 0, "sd", BASE_TRIM, COLON,
+ MD_DISK_PATH, 0, 0, 0, 1, "md", MD_TRIM, COMMA,
+ TAPE_PATH, 0, 0, 0, 0, "st", BASE_TRIM, COLON,
+};
+
+/*
+ * Build a list of disks attached to the system.
+ */
+static void
+build_disk_list(void)
+{
+ ldinfo_t *ptoi;
+
+ /*
+ * Build the list of devices connected to the system.
+ */
+ ptoi = rummage_devinfo();
+ rummage_dev(ptoi);
+ cleanup_ldinfo(ptoi);
+}
+
+/*
+ * Walk the /dev/dsk and /dev/rmt directories building a
+ * list of interesting devices. Interesting is everything in the
+ * /dev/dsk directory. We skip some of the stuff in the /dev/rmt
+ * directory.
+ *
+ * Note that not finding one or more of the directories is not an
+ * error.
+ */
+static void
+rummage_dev(ldinfo_t *ptoi)
+{
+ DIR *dskp;
+ int i;
+ struct stat buf;
+
+ for (i = 0; i < MAX_TYPES; i++) {
+ if (stat(dlist[i].name, &buf) == 0) {
+ if (dlist[i].mtime != buf.st_mtime) {
+ /*
+ * We've found a change. We need to cleanup
+ * old information and then rebuild the list
+ * for this device type.
+ */
+ cleanup_dlist(&dlist[i]);
+ dlist[i].mtime = buf.st_mtime;
+ if ((dskp = opendir(dlist[i].name))) {
+ struct dirent *bpt;
+ char last_snm[NAME_BUFLEN];
+
+ last_snm[0] = NULL;
+ while ((bpt = readdir(dskp)) != NULL) {
+ if (bpt->d_name[0] != '.') {
+ process_dir_ent(
+ bpt->d_name,
+ i, last_snm,
+ &dlist[i],
+ ptoi);
+ }
+ }
+ }
+ (void) closedir(dskp);
+ }
+ }
+ }
+}
+
+/*
+ * Walk the list of located devices and see if we've
+ * seen this device before. We look at the short name.
+ */
+static int
+look_up_name(const char *nm, disk_list_t *list)
+{
+ while (list) {
+ if (strcmp(list->dsk, nm) != 0)
+ list = list->next;
+ else {
+ return (1);
+ }
+ }
+ return (0);
+}
+
+/*
+ * Take a name of the form cNtNdNsN or cNtNdNpN
+ * or /dev/dsk/CNtNdNsN or /dev/dsk/cNtNdNpN
+ * remove the trailing sN or pN. Simply looking
+ * for the first 's' or 'p' doesn't cut it.
+ */
+static void
+do_snm(char *orig, char *shortnm)
+{
+ char *tmp;
+ char *ptmp;
+ int done = 0;
+ char repl_char = 0;
+
+ tmp = strrchr(orig, 's');
+ if (tmp) {
+ ptmp = tmp;
+ ptmp++;
+ done = str_is_digit(ptmp);
+ }
+ if (done == 0) {
+ /*
+ * The string either has no 's' in it
+ * or the stuff trailing the s has a
+ * non-numeric in it. Look to see if
+ * we have an ending 'p' followed by
+ * numerics.
+ */
+ tmp = strrchr(orig, 'p');
+ if (tmp) {
+ ptmp = tmp;
+ ptmp++;
+ if (str_is_digit(ptmp))
+ repl_char = 'p';
+ else
+ tmp = 0;
+ }
+ } else {
+ repl_char = 's';
+ }
+ if (tmp)
+ *tmp = '\0';
+ (void) strcpy(shortnm, orig);
+ if (repl_char)
+ *tmp = repl_char;
+}
+
+/*
+ * Create and insert an entry into the device list.
+ */
+static disk_list_t *
+make_an_entry(char *lname, char *shortnm, char *longnm,
+ dir_info_t *drent, int devtype, ldinfo_t *ptoi)
+{
+ disk_list_t *entry;
+ char *nlnm;
+ char snm[NAME_BUFLEN];
+ ldinfo_t *p;
+
+ entry = safe_alloc(sizeof (disk_list_t));
+
+ nlnm = trim(lname, drent->trimstr, drent->trimchr);
+ entry->dsk = safe_strdup(shortnm);
+ do_snm(longnm, snm);
+ entry->dname = safe_strdup(snm);
+ entry->devtype = devtype;
+ entry->devidstr = NULL;
+ if ((p = find_ldinfo_match(nlnm, ptoi))) {
+ entry->dnum = p->dnum;
+ entry->dtype = safe_strdup(p->dtype);
+ if (p->devidstr)
+ entry->devidstr = safe_strdup(p->devidstr);
+ } else {
+ entry->dtype = safe_strdup(drent->dtype);
+ entry->dnum = -1;
+ if (drent->dtype) {
+ if (strcmp(drent->dtype, "md") == 0) {
+ (void) sscanf(shortnm, "d%d", &entry->dnum);
+ }
+ }
+ }
+ entry->seen = 0;
+ entry->next = 0;
+ insert_dlist_ent(entry, &drent->list);
+ return (entry);
+}
+
+/*
+ * slice stuff off beginning and end of /devices directory names derived from
+ * device links.
+ */
+static char *
+trim(char *fnm, char *lname, int rchr)
+{
+ char *ptr;
+
+ while (*lname == *fnm) {
+ lname++;
+ fnm++;
+ }
+ if ((ptr = strrchr(fnm, rchr)))
+ *ptr = NULL;
+ return (fnm);
+}
+
+/*
+ * Find an entry matching the name passed in
+ */
+static ldinfo_t *
+find_ldinfo_match(char *name, ldinfo_t *ptoi)
+{
+ if (name) {
+ while (ptoi) {
+ if (strcmp(ptoi->name, name))
+ ptoi = ptoi->next;
+ else
+ return (ptoi);
+ }
+ }
+ return (NULL);
+}
+
+/*
+ * Determine if a name is already in the list of disks. If not, insert the
+ * name in the list.
+ */
+static void
+insert_dlist_ent(disk_list_t *n, disk_list_t **hd)
+{
+ disk_list_t *tmp_ptr;
+
+ if (n->dtype != NULL) {
+ tmp_ptr = *hd;
+ while (tmp_ptr) {
+ if (strcmp(n->dsk, tmp_ptr->dsk) != 0)
+ tmp_ptr = tmp_ptr->next;
+ else
+ break;
+ }
+ if (tmp_ptr == NULL) {
+ /*
+ * We don't do anything with MD_DISK types here
+ * since they don't have partitions.
+ */
+ if (n->devtype == DISK || n->devtype == OSA_DISK) {
+ n->flags = SLICES_OK;
+#if defined(i386) || defined(__ia64)
+ n->flags |= PARTITIONS_OK;
+#endif
+ } else {
+ n->flags = 0;
+ }
+ /*
+ * Figure out where to insert the name. The list is
+ * ostensibly in sorted order.
+ */
+ if (*hd) {
+ disk_list_t *follw;
+ int mv;
+
+ tmp_ptr = *hd;
+
+ /*
+ * Look through the list. While the strcmp
+ * value is less than the current value,
+ */
+ while (tmp_ptr) {
+ if ((mv = strcmp(n->dtype,
+ tmp_ptr->dtype)) < 0) {
+ follw = tmp_ptr;
+ tmp_ptr = tmp_ptr->next;
+ } else
+ break;
+ }
+ if (mv == 0) {
+ /*
+ * We're now in the area where the
+ * leading chars of the kstat name
+ * match. We need to insert in numeric
+ * order after that.
+ */
+ while (tmp_ptr) {
+ if (strcmp(n->dtype,
+ tmp_ptr->dtype) != 0)
+ break;
+ if (n->dnum > tmp_ptr->dnum) {
+ follw = tmp_ptr;
+ tmp_ptr = tmp_ptr->next;
+ } else
+ break;
+ }
+ }
+ /*
+ * We should now be ready to insert an
+ * entry...
+ */
+ if (mv >= 0) {
+ if (tmp_ptr == *hd) {
+ n->next = tmp_ptr;
+ *hd = n;
+ } else {
+ n->next = follw->next;
+ follw->next = n;
+ }
+ } else {
+ /*
+ * insert at the end of the
+ * list
+ */
+ follw->next = n;
+ n->next = 0;
+ }
+ } else {
+ *hd = n;
+ n->next = 0;
+ }
+ }
+ }
+}
+
+/*
+ * find an entry matching the given kstat name in the list
+ * of disks, tapes and metadevices.
+ */
+disk_list_t *
+lookup_ks_name(char *dev_nm)
+{
+ int tried = 0;
+ int dv;
+ int len;
+ char cmpbuf[PATH_MAX + 1];
+ struct list_of_disks *list;
+ char *nm;
+ dev_name_t *tmp;
+ uint_t i;
+
+ /*
+ * extract the device type from the kstat name. We expect the
+ * name to be one or more alphabetics followed by the device
+ * numeric id. We do this solely for speed purposes .
+ */
+ len = 0;
+ nm = dev_nm;
+ while (*nm) {
+ if (isalpha(*nm)) {
+ nm++;
+ len++;
+ } else
+ break;
+ }
+
+ if (!*nm)
+ return (NULL);
+
+ /*
+ * For each of the elements in the dlist array we keep
+ * an array of pointers to chains for each of the kstat
+ * prefixes found within that directory. This is typically
+ * 'sd' and 'ssd'. We walk the list in the directory and
+ * match on that type. Since the same prefixes can be
+ * in multiple places we keep checking if we don't find
+ * it in the first place.
+ */
+
+ (void) strncpy(cmpbuf, dev_nm, len);
+ cmpbuf[len] = NULL;
+ dv = atoi(nm);
+
+retry:
+ for (i = 0; i < MAX_TYPES; i++) {
+ tmp = dlist[i].nf;
+ while (tmp) {
+ if (strcmp(tmp->name, cmpbuf) == 0) {
+ /*
+ * As an optimization we keep mins
+ * and maxes for the devices found.
+ * This helps chop the lists up and
+ * avoid some really long chains as
+ * we would get if we kept only prefix
+ * lists.
+ */
+ if (dv >= tmp->min && dv <= tmp->max) {
+ list = tmp->list_start;
+ while (list) {
+ if (list->dnum < dv)
+ list = list->next;
+ else
+ break;
+ }
+ if (list && list->dnum == dv) {
+ return (list);
+ }
+ }
+ }
+ tmp = tmp->next;
+ }
+ }
+
+ if (!tried) {
+ tried = 1;
+ build_disk_list();
+ goto retry;
+ }
+
+ return (0);
+}
+
+static int
+str_is_digit(char *str)
+{
+ while (*str) {
+ if (isdigit(*str))
+ str++;
+ else
+ return (0);
+ }
+ return (1);
+}
+
+static void
+insert_into_dlist(dir_info_t *d, disk_list_t *e)
+{
+ dev_name_t *tmp;
+
+ tmp = d->nf;
+ while (tmp) {
+ if (strcmp(e->dtype, tmp->name) != 0) {
+ tmp = tmp->next;
+ } else {
+ if (e->dnum < tmp->min) {
+ tmp->min = e->dnum;
+ tmp->list_start = e;
+ } else if (e->dnum > tmp->max) {
+ tmp->max = e->dnum;
+ tmp->list_end = e;
+ }
+ break;
+ }
+ }
+ if (tmp == NULL) {
+ tmp = safe_alloc(sizeof (dev_name_t));
+ tmp->name = e->dtype;
+ tmp->min = e->dnum;
+ tmp->max = e->dnum;
+ tmp->list_start = e;
+ tmp->list_end = e;
+ tmp->next = d->nf;
+ d->nf = tmp;
+ }
+}
+
+/*
+ * devinfo_ident_disks() and devinfo_ident_tapes() are the callback functions we
+ * use while walking the device tree snapshot provided by devinfo. If
+ * devinfo_ident_disks() identifies that the device being considered has one or
+ * more minor nodes _and_ is a block device, then it is a potential disk.
+ * Similarly for devinfo_ident_tapes(), except that the second criterion is that
+ * the minor_node be a character device. (This is more inclusive than only
+ * tape devices, but will match any entries in /dev/rmt/.)
+ *
+ * Note: if a driver was previously loaded but is now unloaded, the kstat may
+ * still be around (e.g., st) but no information will be found in the
+ * libdevinfo tree.
+ */
+
+static int
+devinfo_ident_disks(di_node_t node, void *arg)
+{
+ di_minor_t minor = DI_MINOR_NIL;
+
+ if ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
+ int spectype = di_minor_spectype(minor);
+
+ if (S_ISBLK(spectype)) {
+ char *physical_path = di_devfs_path(node);
+ int instance = di_instance(node);
+ char *driver_name = di_driver_name(node);
+ char *devidstr;
+
+ /* lookup the devid, devt specific first */
+ if ((di_prop_lookup_strings(di_minor_devt(minor), node,
+ DEVID_PROP_NAME, &devidstr) == -1) &&
+ (di_prop_lookup_strings(DDI_DEV_T_ANY, node,
+ DEVID_PROP_NAME, &devidstr) == -1))
+ devidstr = NULL;
+
+ if (driver_name == NULL)
+ driver_name = "<nil>";
+
+ pline(physical_path, instance,
+ driver_name, devidstr, arg);
+ di_devfs_path_free(physical_path);
+ }
+ }
+ return (DI_WALK_CONTINUE);
+}
+
+static int
+devinfo_ident_tapes(di_node_t node, void *arg)
+{
+ di_minor_t minor = DI_MINOR_NIL;
+
+ if ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
+ int spectype = di_minor_spectype(minor);
+
+ if (S_ISCHR(spectype)) {
+ char *physical_path = di_devfs_path(node);
+ int instance = di_instance(node);
+ char *binding_name = di_binding_name(node);
+
+ pline(physical_path, instance,
+ binding_name, NULL, arg);
+ di_devfs_path_free(physical_path);
+ }
+ }
+ return (DI_WALK_CONTINUE);
+}
+
+/*
+ * rummage_devinfo() is the driver routine that walks the devinfo snapshot.
+ */
+static ldinfo_t *
+rummage_devinfo(void)
+{
+ di_node_t root_node;
+ ldinfo_t *rv = NULL;
+
+ if ((root_node = di_init("/", DINFOCPYALL)) != DI_NODE_NIL) {
+ (void) di_walk_node(root_node, DI_WALK_CLDFIRST, (void *)&rv,
+ devinfo_ident_disks);
+ (void) di_walk_node(root_node, DI_WALK_CLDFIRST, (void *)&rv,
+ devinfo_ident_tapes);
+ di_fini(root_node);
+ }
+ return (rv);
+}
+
+/*
+ * pline() performs the lookup of the device path in the current list of disks,
+ * and adds the appropriate information to the nms list in the case of a match.
+ */
+static void
+pline(char *devfs_path, int instance,
+ char *driver_name, char *devidstr, ldinfo_t **list)
+{
+ ldinfo_t *entry;
+
+ entry = safe_alloc(sizeof (ldinfo_t));
+ entry->dnum = instance;
+ entry->name = safe_strdup(devfs_path);
+ entry->dtype = safe_strdup(driver_name);
+ entry->devidstr = safe_strdup(devidstr);
+ entry->next = *list;
+ *list = entry;
+}
+
+/*
+ * Cleanup space allocated in dlist processing.
+ * We're only interested in cleaning up the list and nf
+ * fields in the structure. Everything else is static
+ * data.
+ */
+static void
+cleanup_dlist(dir_info_t *d)
+{
+ dev_name_t *tmp;
+ dev_name_t *t1;
+ disk_list_t *t2;
+ disk_list_t *t3;
+
+ /*
+ * All of the entries in a dev_name_t use information
+ * from a disk_list_t structure that is freed later.
+ * All we need do here is free the dev_name_t
+ * structure itself.
+ */
+ tmp = d->nf;
+ while (tmp) {
+ t1 = tmp->next;
+ free(tmp);
+ tmp = t1;
+ }
+ d->nf = 0;
+ /*
+ * "Later". Free the disk_list_t structures and their
+ * data attached to this portion of the dir_info
+ * structure.
+ */
+ t2 = d->list;
+ while (t2) {
+ if (t2->dtype) {
+ free(t2->dtype);
+ t2->dtype = NULL;
+ }
+ if (t2->dsk) {
+ free(t2->dsk);
+ t2->dsk = NULL;
+ }
+ if (t2->dname) {
+ free(t2->dname);
+ t2->dname = NULL;
+ }
+ t3 = t2->next;
+ free(t2);
+ t2 = t3;
+ }
+ d->list = 0;
+}
+
+static void
+process_dir_ent(char *dent, int curr_type, char *last_snm,
+ dir_info_t *dp, ldinfo_t *ptoi)
+{
+ struct stat sbuf;
+ char dnmbuf[PATH_MAX + 1];
+ char lnm[NAME_BUFLEN];
+ char snm[NAME_BUFLEN];
+ char *npt;
+
+ snm[0] = NULL;
+ if (curr_type == DISK || curr_type == OSA_DISK) {
+ /*
+ * get the short name - omitting
+ * the trailing sN or PN
+ */
+ (void) strcpy(lnm, dent);
+ do_snm(dent, snm);
+ } else if (curr_type == MD_DISK) {
+ (void) strcpy(lnm, dent);
+ (void) strcpy(snm, dent);
+ } else {
+ /*
+ * don't want all rewind/etc
+ * devices for a tape
+ */
+ if (!str_is_digit(dent))
+ return;
+ (void) snprintf(snm, sizeof (snm), "rmt/%s", dent);
+ (void) snprintf(lnm, sizeof (snm), "rmt/%s", dent);
+ }
+ /*
+ * See if we've already processed an entry for this device.
+ * If so, we're just another partition so we get another
+ * entry.
+ *
+ * last_snm is an optimization to avoid the function call
+ * and lookup since we'll often see partition records
+ * immediately after the disk record.
+ */
+ if (dp->skip_lookup == 0) {
+ if (strcmp(snm, last_snm) != 0) {
+ /*
+ * a zero return means that
+ * no record was found. We'd
+ * return a pointer otherwise.
+ */
+ if (look_up_name(snm,
+ dp->list) == 0) {
+ (void) strcpy(last_snm, snm);
+ } else
+ return;
+ } else
+ return;
+ }
+ /*
+ * Get the real device name for this beast
+ * by following the link into /devices.
+ */
+ (void) snprintf(dnmbuf, sizeof (dnmbuf), "%s/%s", dp->name, dent);
+ if (lstat(dnmbuf, &sbuf) != -1) {
+ if ((sbuf.st_mode & S_IFMT) == S_IFLNK) {
+ /*
+ * It's a link. Get the real name.
+ */
+ char nmbuf[PATH_MAX + 1];
+ int nbyr;
+
+ if ((nbyr = readlink(dnmbuf, nmbuf,
+ sizeof (nmbuf))) != 1) {
+ npt = nmbuf;
+ /*
+ * readlink does not terminate
+ * the string so we have to
+ * do it.
+ */
+ nmbuf[nbyr] = NULL;
+ } else
+ npt = NULL;
+ } else
+ npt = lnm;
+ /*
+ * make an entry in the device list
+ */
+ if (npt) {
+ disk_list_t *d;
+
+ d = make_an_entry(npt, snm,
+ dnmbuf, dp,
+ curr_type, ptoi);
+ insert_into_dlist(dp, d);
+ }
+ }
+}
+static void
+cleanup_ldinfo(ldinfo_t *list)
+{
+ ldinfo_t *tmp;
+ while (list) {
+ tmp = list;
+ list = list->next;
+ free(tmp->name);
+ free(tmp->dtype);
+ if (tmp->devidstr)
+ free(tmp->devidstr);
+ free(tmp);
+ }
+}
+
+char *
+lookup_nfs_name(char *ks, kstat_ctl_t *kc)
+{
+ int tried = 0;
+ uint_t minor;
+ char *host, *path;
+ char *cp;
+ char *rstr = 0;
+ size_t len;
+
+ if (sscanf(ks, "nfs%u", &minor) == 1) {
+retry:
+ cp = get_nfs_by_minor(minor);
+ if (cp) {
+ if (strchr(cp, ',') == NULL) {
+ rstr = safe_strdup(cp);
+ return (rstr);
+ }
+ host = cur_hostname(minor, kc);
+ if (host) {
+ if (*host) {
+ path = cur_special(host, cp);
+ if (path) {
+ len = strlen(host);
+ len += strlen(path);
+ len += 2;
+ rstr = safe_alloc(len);
+ (void) snprintf(rstr, len,
+ "%s:%s", host, path);
+ } else {
+ rstr = safe_strdup(cp);
+ }
+ } else {
+ rstr = safe_strdup(ks);
+ }
+ free(host);
+ } else {
+ rstr = safe_strdup(cp);
+ }
+ } else if (!tried) {
+ tried = 1;
+ do_mnttab();
+ goto retry;
+ }
+ }
+ return (rstr);
+}
+
+static char *
+get_nfs_by_minor(uint_t minor)
+{
+ mnt_t *localnfs;
+
+ localnfs = nfs;
+ while (localnfs) {
+ if (localnfs->minor == minor) {
+ return (localnfs->device_name);
+ }
+ localnfs = localnfs->next;
+ }
+ return (0);
+}
+
+/*
+ * Read the cur_hostname from the mntinfo kstat
+ */
+static char *
+cur_hostname(uint_t minor, kstat_ctl_t *kc)
+{
+ kstat_t *ksp;
+ static struct mntinfo_kstat mik;
+ char *rstr;
+
+ for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
+ if (ksp->ks_type != KSTAT_TYPE_RAW)
+ continue;
+ if (ksp->ks_instance != minor)
+ continue;
+ if (strcmp(ksp->ks_module, "nfs"))
+ continue;
+ if (strcmp(ksp->ks_name, "mntinfo"))
+ continue;
+ if (ksp->ks_flags & KSTAT_FLAG_INVALID)
+ return (NULL);
+ if (kstat_read(kc, ksp, &mik) == -1)
+ return (NULL);
+ rstr = safe_strdup(mik.mik_curserver);
+ return (rstr);
+ }
+ return (NULL);
+}
+
+/*
+ * Given the hostname of the mounted server, extract the server
+ * mount point from the mnttab string.
+ *
+ * Common forms:
+ * server1,server2,server3:/path
+ * server1:/path,server2:/path
+ * or a hybrid of the two
+ */
+static char *
+cur_special(char *hostname, char *special)
+{
+ char *cp;
+ char *path;
+ size_t hlen = strlen(hostname);
+
+ /*
+ * find hostname in string
+ */
+again:
+ if ((cp = strstr(special, hostname)) == NULL)
+ return (NULL);
+
+ /*
+ * hostname must be followed by ',' or ':'
+ */
+ if (cp[hlen] != ',' && cp[hlen] != ':') {
+ special = &cp[hlen];
+ goto again;
+ }
+
+ /*
+ * If hostname is followed by a ',' eat all characters until a ':'
+ */
+ cp = &cp[hlen];
+ if (*cp == ',') {
+ cp++;
+ while (*cp != ':') {
+ if (*cp == NULL)
+ return (NULL);
+ cp++;
+ }
+ }
+ path = ++cp; /* skip ':' */
+
+ /*
+ * path is terminated by either 0, or space or ','
+ */
+ while (*cp) {
+ if (isspace(*cp) || *cp == ',') {
+ *cp = NULL;
+ return (path);
+ }
+ cp++;
+ }
+ return (path);
+}
diff --git a/usr/src/cmd/stat/common/dsr.h b/usr/src/cmd/stat/common/dsr.h
new file mode 100644
index 0000000000..dc14576efb
--- /dev/null
+++ b/usr/src/cmd/stat/common/dsr.h
@@ -0,0 +1,142 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _STAT_DSR_H
+#define _STAT_DSR_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Description of each device identified
+ */
+typedef struct list_of_disks {
+ char *dtype; /* device type: sd, ssd, md, st, etc. */
+ int dnum; /* device number */
+ char *dsk; /* in form of cNtNdN */
+ char *dname; /* in form of /dev/dsk/cNtNdN */
+ char *devidstr; /* in form of "id1,sd@XXXX" */
+ uint_t flags; /* see SLICES_OK and PARTITIONS_OK above */
+ int devtype; /* disk, metadevice, tape */
+ uint_t seen; /* Used for diffing disk lists */
+ struct list_of_disks *next; /* link to next one */
+} disk_list_t;
+
+/*
+ * Description of each mount point currently existing on the system.
+ */
+typedef struct mnt_info {
+ char *device_name;
+ char *mount_point;
+ char *devinfo;
+ uint_t minor;
+ struct mnt_info *next;
+} mnt_t;
+
+/*
+ * A basic description of each device found
+ * on the system by walking the device tree.
+ * These entries are used to select the
+ * relevent entries from the actual /dev
+ * entries.
+ */
+typedef struct ldinfo {
+ char *name;
+ char *dtype;
+ char *devidstr;
+ int dnum;
+ struct ldinfo *next;
+} ldinfo_t;
+
+/*
+ * Optimization for lookup of kstats.
+ * For each kstat prefix (e.g., 'sd')
+ * found in a directory one of these
+ * structures will be created.
+ *
+ * name: prefix of kstat name (e.g., 'ssd')
+ * min: smallest number seen from kstat
+ * name (e.g., 101 from 'sd101')
+ * max: largest number seen from kstat
+ * list_start: beginning of disk_list structures
+ * for this kstat type in the main list for
+ * this directory
+ * list_end: end of entries for this kstat type
+ * in this directory.
+ */
+typedef struct dev_name {
+ char *name;
+ uint_t min;
+ uint_t max;
+ disk_list_t *list_start;
+ disk_list_t *list_end;
+ struct dev_name *next;
+} dev_name_t;
+
+/*
+ * Definition of a "type" of disk device.
+ * Tied to the directory containing entries
+ * for that device. Divides the list of
+ * devices into localized chunks and allows
+ * quick determination as to whether an entry
+ * exists or whether we need to look at the
+ * devices upon a state change.
+ */
+typedef struct dir_info {
+ char *name; /* directory name */
+ time_t mtime; /* mod time */
+ disk_list_t *list; /* master list of devices */
+ dev_name_t *nf; /* lists per name */
+ uint_t skip_lookup; /* skip lookup if device */
+ /* does not have partitions */
+ char *dtype; /* Type of device */
+ char *trimstr; /* What do we prune */
+ char trimchr; /* Char denoting end */
+ /* of interesting data */
+} dir_info_t;
+
+/*
+ * The following are used to control treatment of kstat names
+ * which fall beyond the number of disk partitions allowed on
+ * the particular ISA. PARTITIONS_OK is set only on an Intel
+ * system.
+ */
+#define SLICES_OK 1
+#define PARTITIONS_OK 2
+
+void do_mnttab(void);
+mnt_t *lookup_mntent_byname(char *);
+disk_list_t *lookup_ks_name(char *);
+char *lookup_nfs_name(char *, kstat_ctl_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _STAT_DSR_H */
diff --git a/usr/src/cmd/stat/common/mnt.c b/usr/src/cmd/stat/common/mnt.c
new file mode 100644
index 0000000000..94700fcf1d
--- /dev/null
+++ b/usr/src/cmd/stat/common/mnt.c
@@ -0,0 +1,161 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <sys/mnttab.h>
+#include <sys/mntent.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "statcommon.h"
+#include "dsr.h"
+
+static time_t mtime;
+mnt_t *nfs;
+static mnt_t *ufs;
+
+static void build_mnt_list(FILE *);
+
+mnt_t *
+lookup_mntent_byname(char *nm)
+{
+ mnt_t *rv = 0;
+ mnt_t *minfo;
+ uint_t did_nfs;
+
+
+ if (nm) {
+ minfo = ufs;
+ did_nfs = 0;
+ while (minfo) {
+ if (strcmp(nm, minfo->device_name)) {
+ if (minfo->next != 0)
+ minfo = minfo->next;
+ else if (did_nfs == 0) {
+ minfo = nfs;
+ did_nfs = 1;
+ }
+ else
+ minfo = 0;
+ } else {
+ rv = minfo;
+ break;
+ }
+ }
+ }
+ return (rv);
+}
+
+void
+do_mnttab(void)
+{
+ struct stat buf;
+ FILE *mpt;
+ struct flock lb;
+
+ if (stat(MNTTAB, &buf) == 0) {
+ if (buf.st_mtime != mtime) {
+ /*
+ * File has changed. Get the new file.
+ */
+ if ((mpt = fopen(MNTTAB, "r"))) {
+ lb.l_type = F_RDLCK;
+ lb.l_whence = 0;
+ lb.l_start = 0;
+ lb.l_len = 0;
+ (void) fcntl(fileno(mpt), F_SETLKW, &lb);
+ build_mnt_list(mpt);
+ mtime = buf.st_mtime;
+ /*
+ * Lock goes away when we close the file.
+ */
+ (void) fclose(mpt);
+ }
+ }
+ }
+}
+
+static void
+build_mnt_list(FILE *mpt)
+{
+ mnt_t *item;
+ mnt_t **which;
+ mnt_t *tmp;
+ int found;
+ struct extmnttab mnt;
+
+ if (mpt) {
+ while (nfs) {
+ free(nfs->device_name);
+ free(nfs->mount_point);
+ free(nfs->devinfo);
+ tmp = nfs;
+ nfs = nfs->next;
+ free(tmp);
+ }
+ while (ufs) {
+ free(ufs->device_name);
+ free(ufs->mount_point);
+ free(ufs->devinfo);
+ tmp = ufs;
+ ufs = ufs->next;
+ free(tmp);
+ }
+ (void) memset(&mnt, 0, sizeof (struct extmnttab));
+
+ resetmnttab(mpt);
+ while ((found = getextmntent(mpt, &mnt,
+ sizeof (struct extmnttab))) != -1) {
+ if (found == 0) {
+ if (strcmp(mnt.mnt_fstype, MNTTYPE_UFS) == 0)
+ which = &ufs;
+ else if (strcmp(mnt.mnt_fstype,
+ MNTTYPE_NFS) == 0)
+ which = &nfs;
+ else
+ which = 0;
+ if (which) {
+ item = safe_alloc(sizeof (mnt_t));
+ item->device_name =
+ safe_strdup(mnt.mnt_special);
+ item->mount_point =
+ safe_strdup(mnt.mnt_mountp);
+ item->devinfo =
+ safe_strdup(mnt.mnt_mntopts);
+ item->minor = mnt.mnt_minor;
+ item->next = *which;
+ *which = item;
+ }
+ }
+ }
+ }
+}
diff --git a/usr/src/cmd/stat/common/statcommon.h b/usr/src/cmd/stat/common/statcommon.h
new file mode 100644
index 0000000000..efc48e57ae
--- /dev/null
+++ b/usr/src/cmd/stat/common/statcommon.h
@@ -0,0 +1,306 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Common routines for acquiring snapshots of kstats for
+ * iostat, mpstat, and vmstat.
+ */
+
+#ifndef _STATCOMMON_H
+#define _STATCOMMON_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <kstat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/buf.h>
+#include <sys/dnlc.h>
+#include <sys/sysinfo.h>
+#include <sys/vmmeter.h>
+#include <sys/processor.h>
+#include <sys/pset.h>
+
+/* No CPU present at this CPU position */
+#define ID_NO_CPU -1
+/* CPU belongs to no pset (we number this as "pset 0") */
+#define ID_NO_PSET 0
+/* CPU is usable */
+#define CPU_ONLINE(s) ((s) == P_ONLINE || (s) == P_NOINTR)
+/* will the CPU have kstats */
+#define CPU_ACTIVE(c) (CPU_ONLINE((c)->cs_state) && (c)->cs_id != ID_NO_CPU)
+/* IO device has no identified ID */
+#define IODEV_NO_ID -1
+/* no limit to iodevs to collect */
+#define UNLIMITED_IODEVS ((size_t)-1)
+
+enum snapshot_types {
+ /* All CPUs separately */
+ SNAP_CPUS = 1 << 0,
+ /* Aggregated processor sets */
+ SNAP_PSETS = 1 << 1,
+ /* sys-wide stats including aggregated CPU stats */
+ SNAP_SYSTEM = 1 << 2,
+ /* interrupt sources and counts */
+ SNAP_INTERRUPTS = 1 << 3,
+ /* cache flushes */
+ SNAP_FLUSHES = 1 << 4,
+ /* disk etc. stats */
+ SNAP_IODEVS = 1 << 5,
+ /* disk controller aggregates */
+ SNAP_CONTROLLERS = 1 << 6,
+ /* mpxio (multipath) paths */
+ SNAP_IOPATHS = 1 << 7,
+ /* disk error stats */
+ SNAP_IODEV_ERRORS = 1 << 8,
+ /* pretty names for iodevs */
+ SNAP_IODEV_PRETTY = 1 << 9
+};
+
+struct cpu_snapshot {
+ /* may be ID_NO_CPU if no CPU present */
+ processorid_t cs_id;
+ /* may be ID_NO_PSET if no pset */
+ psetid_t cs_pset_id;
+ /* as in p_online(2) */
+ int cs_state;
+ /* stats for this CPU */
+ kstat_t cs_vm;
+ kstat_t cs_sys;
+};
+
+struct pset_snapshot {
+ /* ID may be zero to indicate the "none set" */
+ psetid_t ps_id;
+ /* number of CPUs in set */
+ size_t ps_nr_cpus;
+ /* the CPUs in this set */
+ struct cpu_snapshot **ps_cpus;
+};
+
+struct intr_snapshot {
+ /* name of interrupt source */
+ char is_name[KSTAT_STRLEN];
+ /* total number of interrupts from this source */
+ ulong_t is_total;
+};
+
+struct sys_snapshot {
+ sysinfo_t ss_sysinfo;
+ vminfo_t ss_vminfo;
+ struct nc_stats ss_nc;
+ /* vm/sys stats aggregated across all CPUs */
+ kstat_t ss_agg_vm;
+ kstat_t ss_agg_sys;
+ /* ticks since boot */
+ ulong_t ss_ticks;
+ long ss_deficit;
+};
+
+/* order is significant (see sort_before()) */
+enum iodev_type {
+ IODEV_CONTROLLER = 1 << 0,
+ IODEV_DISK = 1 << 1,
+ IODEV_PARTITION = 1 << 2,
+ IODEV_TAPE = 1 << 3,
+ IODEV_NFS = 1 << 4,
+ IODEV_IOPATH = 1 << 5,
+ IODEV_UNKNOWN = 1 << 6
+};
+
+/* identify a disk, partition, etc. */
+struct iodev_id {
+ int id;
+ /* target id (for disks) */
+ char tid[KSTAT_STRLEN];
+};
+
+/*
+ * Used for disks, partitions, tapes, nfs, controllers, iopaths
+ * Each entry can be a branch of a tree; for example, the disks
+ * of a controller constitute the children of the controller
+ * iodev_snapshot. This relationship is not strictly maintained
+ * if is_pretty can't be found.
+ */
+struct iodev_snapshot {
+ /* original kstat name */
+ char is_name[KSTAT_STRLEN];
+ enum iodev_type is_type;
+ /* ID if meaningful */
+ struct iodev_id is_id;
+ /* parent ID if meaningful */
+ struct iodev_id is_parent_id;
+ /* user-friendly name if found */
+ char *is_pretty;
+ /* device ID if applicable */
+ char *is_devid;
+ /* mount-point if applicable */
+ char *is_dname;
+ /* number of direct children */
+ int is_nr_children;
+ /* children of this I/O device */
+ struct iodev_snapshot *is_children;
+ /* standard I/O stats */
+ kstat_io_t is_stats;
+ /* iodev error stats */
+ kstat_t is_errors;
+ /* creation time of the stats */
+ hrtime_t is_crtime;
+ /* time at which iodev snapshot was taken */
+ hrtime_t is_snaptime;
+ /* kstat module */
+ char is_module[KSTAT_STRLEN];
+ /* kstat instance */
+ int is_instance;
+ /* kstat (only used temporarily) */
+ kstat_t *is_ksp;
+ struct iodev_snapshot *is_prev;
+ struct iodev_snapshot *is_next;
+};
+
+/* which iodevs to show. */
+struct iodev_filter {
+ /* nr. of iodevs to choose */
+ size_t if_max_iodevs;
+ /* bit mask of enum io_types to allow */
+ int if_allowed_types;
+ /* should we show floppy ? if_names can override this */
+ int if_skip_floppy;
+ /* nr. of named iodevs */
+ size_t if_nr_names;
+ char **if_names;
+};
+
+/* The primary structure of a system snapshot. */
+struct snapshot {
+ /* what types were *requested* */
+ enum snapshot_types s_types;
+ size_t s_nr_cpus;
+ struct cpu_snapshot *s_cpus;
+ size_t s_nr_psets;
+ struct pset_snapshot *s_psets;
+ size_t s_nr_intrs;
+ struct intr_snapshot *s_intrs;
+ size_t s_nr_iodevs;
+ struct iodev_snapshot *s_iodevs;
+ struct sys_snapshot s_sys;
+ struct biostats s_biostats;
+ struct flushmeter s_flushes;
+};
+
+/* print a message and exit with failure */
+void fail(int do_perror, char *message, ...);
+
+/* strdup str, or exit with failure */
+char *safe_strdup(char *str);
+
+/* malloc successfully, or exit with failure */
+void *safe_alloc(size_t size);
+
+/*
+ * Copy a kstat from src to dst. If the source kstat contains no data,
+ * then set the destination kstat data to NULL and size to zero.
+ * Returns 0 on success.
+ */
+int kstat_copy(const kstat_t *src, kstat_t *dst);
+
+/*
+ * Look up the named kstat, and give the ui64 difference i.e.
+ * new - old, or if old is NULL, return new.
+ */
+uint64_t kstat_delta(kstat_t *old, kstat_t *new, char *name);
+
+/*
+ * Add the integer-valued stats from "src" to the
+ * existing ones in "dst". If "dst" does not contain
+ * stats, then a kstat_copy() is performed.
+ */
+int kstat_add(const kstat_t *src, kstat_t *dst);
+
+/* return the number of CPUs with kstats (i.e. present and online) */
+int nr_active_cpus(struct snapshot *ss);
+
+/*
+ * Return the difference in CPU ticks between the two sys
+ * kstats.
+ */
+uint64_t cpu_ticks_delta(kstat_t *old, kstat_t *new);
+
+/*
+ * Open the kstat chain. Cannot fail.
+ */
+kstat_ctl_t *open_kstat(void);
+
+/*
+ * Return a struct snapshot based on the snapshot_types parameter
+ * passed in. iodev_filter may be NULL in which case all iodevs
+ * are selected if SNAP_IODEVS is passed.
+ */
+struct snapshot *acquire_snapshot(kstat_ctl_t *, int, struct iodev_filter *);
+
+/* free a snapshot */
+void free_snapshot(struct snapshot *ss);
+
+typedef void (*snapshot_cb)(void *old, void *new, void *data);
+
+/*
+ * Call the call back for each pair of data items of the given type,
+ * passing the data pointer passed in as well. If an item has been
+ * added, the first pointer will be NULL; if removed, the second pointer
+ * will be NULL.
+ *
+ * A non-zero return value indicates configuration has changed.
+ */
+int snapshot_walk(enum snapshot_types type, struct snapshot *old,
+ struct snapshot *new, snapshot_cb cb, void *data);
+
+/*
+ * Output a line detailing any configuration changes such as a CPU
+ * brought online, etc, bracketed by << >>.
+ */
+void snapshot_report_changes(struct snapshot *old, struct snapshot *new);
+
+/* Return non-zero if configuration has changed. */
+int snapshot_has_changed(struct snapshot *old, struct snapshot *new);
+
+/* free the given iodev */
+void free_iodev(struct iodev_snapshot *iodev);
+
+/* acquire the I/O devices */
+int acquire_iodevs(struct snapshot *ss, kstat_ctl_t *kc,
+ struct iodev_filter *df);
+
+/* strcmp-style I/O device comparator */
+int iodev_cmp(struct iodev_snapshot *io1, struct iodev_snapshot *io2);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _STATCOMMON_H */
diff --git a/usr/src/cmd/stat/common/walkers.c b/usr/src/cmd/stat/common/walkers.c
new file mode 100644
index 0000000000..c01a52be6e
--- /dev/null
+++ b/usr/src/cmd/stat/common/walkers.c
@@ -0,0 +1,394 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "statcommon.h"
+
+#include <string.h>
+#include <errno.h>
+
+/* max size of report change annotations */
+#define LIST_SIZE 512
+
+static char cpus_added[LIST_SIZE];
+static char cpus_removed[LIST_SIZE];
+
+static int
+cpu_walk(struct snapshot *old, struct snapshot *new,
+ snapshot_cb cb, void *data)
+{
+ int changed = 0;
+ int i;
+
+ /* CPUs can change state but not re-order */
+ for (i = 0; i < new->s_nr_cpus; i++) {
+ struct cpu_snapshot *cpu = NULL;
+ struct cpu_snapshot *newcpu = &new->s_cpus[i];
+ if (old)
+ cpu = &old->s_cpus[i];
+ cb(cpu, newcpu, data);
+ if (cpu == NULL)
+ changed = 1;
+ else {
+ /*
+ * We only care about off/on line transitions
+ */
+ if ((CPU_ACTIVE(cpu) && !CPU_ACTIVE(newcpu)) ||
+ (!CPU_ACTIVE(cpu) && CPU_ACTIVE(newcpu)))
+ changed = 1;
+ if ((new->s_types & SNAP_PSETS) &&
+ cpu->cs_pset_id != newcpu->cs_pset_id)
+ changed = 1;
+ }
+
+ }
+
+ return (changed);
+}
+
+static int
+pset_walk(struct snapshot *old, struct snapshot *new,
+ snapshot_cb cb, void *data)
+{
+ int i = 0;
+ int j = 0;
+ int changed = 0;
+
+ while (old && i < old->s_nr_psets && j < new->s_nr_psets) {
+ if (old->s_psets[i].ps_id < new->s_psets[j].ps_id) {
+ cb(&old->s_psets[i], NULL, data);
+ i++;
+ changed = 1;
+ } else if (old->s_psets[i].ps_id > new->s_psets[j].ps_id) {
+ cb(NULL, &new->s_psets[j], data);
+ j++;
+ changed = 1;
+ } else {
+ cb(&old->s_psets[i], &new->s_psets[j], data);
+ i++;
+ j++;
+ }
+ }
+
+ while (old && i < old->s_nr_psets) {
+ cb(&old->s_psets[i], NULL, data);
+ i++;
+ changed = 1;
+ }
+
+ while (j < new->s_nr_psets) {
+ cb(NULL, &new->s_psets[j], data);
+ j++;
+ changed = 1;
+ }
+
+ return (changed);
+}
+
+static int
+iodev_walk(struct iodev_snapshot *d1, struct iodev_snapshot *d2,
+ snapshot_cb cb, void *data)
+{
+ int changed = 0;
+
+ while (d1 && d2) {
+ if (strcmp(d1->is_name, d2->is_name) < 0) {
+ changed = 1;
+ cb(d1, NULL, data);
+ (void) iodev_walk(d1->is_children, NULL, cb, data);
+ d1 = d1->is_next;
+ } else if (strcmp(d1->is_name, d2->is_name) > 0) {
+ changed = 1;
+ cb(NULL, d2, data);
+ (void) iodev_walk(NULL, d2->is_children, cb, data);
+ d2 = d2->is_next;
+ } else {
+ cb(d1, d2, data);
+ changed |= iodev_walk(d1->is_children,
+ d2->is_children, cb, data);
+ d1 = d1->is_next;
+ d2 = d2->is_next;
+ }
+ }
+
+ while (d1) {
+ changed = 1;
+ cb(d1, NULL, data);
+ (void) iodev_walk(d1->is_children, NULL, cb, data);
+ d1 = d1->is_next;
+ }
+
+ while (d2) {
+ changed = 1;
+ cb(NULL, d2, data);
+ (void) iodev_walk(NULL, d2->is_children, cb, data);
+ d2 = d2->is_next;
+ }
+
+ return (changed);
+}
+
+int
+snapshot_walk(enum snapshot_types type, struct snapshot *old,
+ struct snapshot *new, snapshot_cb cb, void *data)
+{
+ int changed = 0;
+
+ switch (type) {
+ case SNAP_CPUS:
+ changed = cpu_walk(old, new, cb, data);
+ break;
+
+ case SNAP_PSETS:
+ changed = pset_walk(old, new, cb, data);
+ break;
+
+ case SNAP_CONTROLLERS:
+ case SNAP_IODEVS:
+ case SNAP_IOPATHS:
+ changed = iodev_walk(old ? old->s_iodevs : NULL,
+ new->s_iodevs, cb, data);
+ break;
+
+ default:
+ break;
+ }
+
+ return (changed);
+}
+
+static void
+add_nr_to_list(char *buf, unsigned long nr)
+{
+ char tmp[LIST_SIZE];
+
+ (void) snprintf(tmp, LIST_SIZE, "%lu", nr);
+
+ if (strlen(buf))
+ (void) strlcat(buf, ", ", LIST_SIZE);
+
+ (void) strlcat(buf, tmp, LIST_SIZE);
+}
+
+static void
+cpu_report(void *v1, void *v2, void *data)
+{
+ int *pset = (int *)data;
+ struct cpu_snapshot *c1 = (struct cpu_snapshot *)v1;
+ struct cpu_snapshot *c2 = (struct cpu_snapshot *)v2;
+
+ if (*pset && c1->cs_pset_id != c2->cs_pset_id) {
+ (void) printf("<<processor %d moved from pset: %d to: %d>>\n",
+ c1->cs_id, c1->cs_pset_id, c2->cs_pset_id);
+ }
+
+ if (c1->cs_state == c2->cs_state)
+ return;
+
+ if (CPU_ONLINE(c1->cs_state) && !CPU_ONLINE(c2->cs_state))
+ add_nr_to_list(cpus_removed, c1->cs_id);
+
+ if (!CPU_ONLINE(c1->cs_state) && CPU_ONLINE(c2->cs_state))
+ add_nr_to_list(cpus_added, c2->cs_id);
+}
+
+/*ARGSUSED*/
+static void
+pset_report(void *v1, void *v2, void *data)
+{
+ struct pset_snapshot *p1 = (struct pset_snapshot *)v1;
+ struct pset_snapshot *p2 = (struct pset_snapshot *)v2;
+
+ if (p2 == NULL) {
+ (void) printf("<<pset destroyed: %u>>\n", p1->ps_id);
+ return;
+ }
+
+ if (p1 == NULL)
+ (void) printf("<<pset created: %u>>\n", p2->ps_id);
+}
+
+static void
+get_child_list(struct iodev_snapshot *iodev, char *buf)
+{
+ char tmp[LIST_SIZE];
+ struct iodev_snapshot *pos = iodev->is_children;
+
+ while (pos) {
+ if (pos->is_type == IODEV_PARTITION) {
+ add_nr_to_list(buf, pos->is_id.id);
+ } else if (pos->is_type == IODEV_DISK) {
+ if (strlen(buf))
+ (void) strlcat(buf, ", ", LIST_SIZE);
+ (void) strlcat(buf, "t", LIST_SIZE);
+ (void) strlcat(buf, pos->is_id.tid, LIST_SIZE);
+ (void) strlcat(buf, "d", LIST_SIZE);
+ *tmp = '\0';
+ add_nr_to_list(tmp, pos->is_id.id);
+ (void) strlcat(buf, tmp, LIST_SIZE);
+ }
+ pos = pos->is_next;
+ }
+}
+
+static void
+iodev_changed(struct iodev_snapshot *iodev, int added)
+{
+ char tmp[LIST_SIZE];
+ int is_disk = iodev->is_type == IODEV_DISK;
+ char *name = iodev->is_name;
+
+ if (iodev->is_pretty)
+ name = iodev->is_pretty;
+
+ switch (iodev->is_type) {
+ case IODEV_IOPATH:
+ (void) printf("<<multi-path %s: %s>>\n",
+ added ? "added" : "removed", name);
+ break;
+ case IODEV_PARTITION:
+ (void) printf("<<partition %s: %s>>\n",
+ added ? "added" : "removed", name);
+ break;
+ case IODEV_NFS:
+ (void) printf("<<NFS %s: %s>>\n",
+ added ? "mounted" : "unmounted", name);
+ break;
+ case IODEV_TAPE:
+ (void) printf("<<device %s: %s>>\n",
+ added ? "added" : "removed", name);
+ break;
+ case IODEV_CONTROLLER:
+ case IODEV_DISK:
+ *tmp = '\0';
+ get_child_list(iodev, tmp);
+ (void) printf("<<%s %s: %s", is_disk ? "disk" : "controller",
+ added ? "added" : "removed", name);
+ if (!*tmp) {
+ (void) printf(">>\n");
+ return;
+ }
+ (void) printf(" (%s %s)>>\n", is_disk ? "slices" : "disks",
+ tmp);
+ break;
+ };
+}
+
+static void
+iodev_report(struct iodev_snapshot *d1, struct iodev_snapshot *d2)
+{
+ while (d1 && d2) {
+ if (iodev_cmp(d1, d2) < 0) {
+ iodev_changed(d1, 0);
+ d1 = d1->is_next;
+ } else if (iodev_cmp(d1, d2) > 0) {
+ iodev_changed(d2, 1);
+ d2 = d2->is_next;
+ } else {
+ iodev_report(d1->is_children, d2->is_children);
+ d1 = d1->is_next;
+ d2 = d2->is_next;
+ }
+ }
+
+ while (d1) {
+ iodev_changed(d1, 0);
+ d1 = d1->is_next;
+ }
+
+ while (d2) {
+ iodev_changed(d2, 1);
+ d2 = d2->is_next;
+ }
+}
+
+void
+snapshot_report_changes(struct snapshot *old, struct snapshot *new)
+{
+ int pset;
+
+ if (old == NULL || new == NULL)
+ return;
+
+ if (old->s_types != new->s_types)
+ return;
+
+ pset = old->s_types & SNAP_PSETS;
+
+ cpus_removed[0] = '\0';
+ cpus_added[0] = '\0';
+
+ if (old->s_types & SNAP_CPUS)
+ (void) snapshot_walk(SNAP_CPUS, old, new, cpu_report, &pset);
+
+ if (cpus_added[0]) {
+ (void) printf("<<processors added: %s>>\n",
+ cpus_added);
+ }
+ if (cpus_removed[0]) {
+ (void) printf("<<processors removed: %s>>\n",
+ cpus_removed);
+ }
+ if (pset) {
+ (void) snapshot_walk(SNAP_PSETS, old, new,
+ pset_report, NULL);
+ }
+
+ iodev_report(old->s_iodevs, new->s_iodevs);
+}
+
+/*ARGSUSED*/
+static void
+dummy_cb(void *v1, void *v2, void *data)
+{
+}
+
+int
+snapshot_has_changed(struct snapshot *old, struct snapshot *new)
+{
+ int ret = 0;
+ int cpu_mask = SNAP_CPUS | SNAP_PSETS | SNAP_SYSTEM;
+ int iodev_mask = SNAP_CONTROLLERS | SNAP_IODEVS | SNAP_IOPATHS;
+
+ if (old == NULL)
+ return (1);
+
+ if (new == NULL)
+ return (EINVAL);
+
+ if (old->s_types != new->s_types)
+ return (EINVAL);
+
+ if (!ret && (old->s_types & cpu_mask))
+ ret = snapshot_walk(SNAP_CPUS, old, new, dummy_cb, NULL);
+ if (!ret && (old->s_types & SNAP_PSETS))
+ ret = snapshot_walk(SNAP_PSETS, old, new, dummy_cb, NULL);
+ if (!ret && (old->s_types & iodev_mask))
+ ret = snapshot_walk(SNAP_IODEVS, old, new, dummy_cb, NULL);
+
+ return (ret);
+}
diff --git a/usr/src/cmd/stat/iostat/Makefile b/usr/src/cmd/stat/iostat/Makefile
new file mode 100644
index 0000000000..54c8ae6540
--- /dev/null
+++ b/usr/src/cmd/stat/iostat/Makefile
@@ -0,0 +1,62 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (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 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+PROG = iostat
+OBJS = iostat.o
+SRCS =$(OBJS:%.o=%.c) $(COMMON_SRCS)
+
+include $(SRC)/cmd/Makefile.cmd
+include $(SRC)/cmd/stat/Makefile.stat
+
+LDLIBS += -lkstat -ldevinfo -lrt
+CFLAGS += $(CCVERBOSE) -I${STATCOMMONDIR}
+FILEMODE= 0555
+GROUP= bin
+
+lint := LINTFLAGS = -muxs -I$(STATCOMMONDIR)
+
+.KEEP_STATE:
+
+all: $(PROG)
+
+install: all $(ROOTPROG)
+
+$(PROG): $(OBJS) $(COMMON_OBJS)
+ $(LINK.c) -o $(PROG) $(OBJS) $(COMMON_OBJS) $(LDLIBS)
+ $(POST_PROCESS)
+
+%.o : $(STATCOMMONDIR)/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
+
+clean:
+ -$(RM) $(OBJS) $(COMMON_OBJS)
+
+lint: lint_SRCS
+
+include $(SRC)/cmd/Makefile.targ
diff --git a/usr/src/cmd/stat/iostat/iostat.c b/usr/src/cmd/stat/iostat/iostat.c
new file mode 100644
index 0000000000..4999e20597
--- /dev/null
+++ b/usr/src/cmd/stat/iostat/iostat.c
@@ -0,0 +1,1769 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * rewritten from UCB 4.13 83/09/25
+ * rewritten from SunOS 4.1 SID 1.18 89/10/06
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <memory.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/sysinfo.h>
+#include <inttypes.h>
+#include <strings.h>
+#include <sys/systeminfo.h>
+#include <kstat.h>
+
+#include "dsr.h"
+#include "statcommon.h"
+
+#define DISK_OLD 0x0001
+#define DISK_NEW 0x0002
+#define DISK_EXTENDED 0x0004
+#define DISK_ERRORS 0x0008
+#define DISK_EXTENDED_ERRORS 0x0010
+#define DISK_IOPATH 0x0020
+#define DISK_NORMAL (DISK_OLD | DISK_NEW)
+
+#define DISK_IO_MASK (DISK_OLD | DISK_NEW | DISK_EXTENDED)
+#define DISK_ERROR_MASK (DISK_ERRORS | DISK_EXTENDED_ERRORS)
+#define PRINT_VERTICAL (DISK_ERROR_MASK | DISK_EXTENDED | DISK_IOPATH)
+
+#define REPRINT 19
+
+/*
+ * It's really a pseudo-gigabyte. We use 1000000000 bytes so that the disk
+ * labels don't look bad. 1GB is really 1073741824 bytes.
+ */
+#define DISK_GIGABYTE 1000000000.0
+
+/*
+ * Function desciptor to be called when extended
+ * headers are used.
+ */
+typedef struct formatter {
+ void (*nfunc)(void);
+ struct formatter *next;
+} format_t;
+
+/*
+ * Used to get formatting right when printing tty/cpu
+ * data to the right of disk data
+ */
+enum show_disk_mode {
+ SHOW_FIRST_ONLY,
+ SHOW_SECOND_ONWARDS,
+ SHOW_ALL
+};
+
+enum show_disk_mode show_disk_mode = SHOW_ALL;
+
+char cmdname[] = "iostat";
+
+static char one_blank[] = " ";
+static char two_blanks[] = " ";
+
+/*
+ * count for number of lines to be emitted before a header is
+ * shown again. Only used for the basic format.
+ */
+static uint_t tohdr = 1;
+
+/*
+ * If we're in raw format, have we printed a header? We only do it
+ * once for raw but we emit it every REPRINT lines in non-raw format.
+ * This applies only for the basic header. The extended header is
+ * done only once in both formats.
+ */
+static uint_t hdr_out;
+
+/*
+ * Flags representing arguments from command line
+ */
+static uint_t do_tty; /* show tty info (-t) */
+static uint_t do_disk; /* show disk info per selected */
+ /* format (-d, -D, -e, -E, -x -X) */
+static uint_t do_cpu; /* show cpu info (-c) */
+static uint_t do_interval; /* do intervals (-I) */
+static int do_partitions; /* per-partition stats (-p) */
+static int do_partitions_only; /* per-partition stats only (-P) */
+ /* no per-device stats for disks */
+static uint_t do_conversions; /* display disks as cXtYdZ (-n) */
+static uint_t do_megabytes; /* display data in MB/sec (-M) */
+static uint_t do_controller; /* display controller info (-C) */
+static uint_t do_raw; /* emit raw format (-r) */
+static uint_t do_timestamp; /* timestamp each display (-T) */
+static uint_t do_devid; /* -E should show devid */
+
+/*
+ * Definition of allowable types of timestamps
+ */
+#define CDATE 1
+#define UDATE 2
+
+/*
+ * Default number of disk drives to be displayed in basic format
+ */
+#define DEFAULT_LIMIT 4
+
+struct iodev_filter df;
+
+static uint_t suppress_state; /* skip state change messages */
+static uint_t suppress_zero; /* skip zero valued lines */
+static uint_t show_mountpts; /* show mount points */
+static int interval; /* interval (seconds) to output */
+static int iter; /* iterations from command line */
+
+#define SMALL_SCRATCH_BUFLEN 64
+#define DISPLAYED_NAME_FORMAT "%-9.9s"
+
+static char disk_header[132];
+static uint_t dh_len; /* disk header length for centering */
+static int lineout; /* data waiting to be printed? */
+
+static struct snapshot *newss;
+static struct snapshot *oldss;
+static double getime; /* elapsed time */
+static double percent; /* 100 / etime */
+
+/*
+ * List of functions to be called which will construct the desired output
+ */
+static format_t *formatter_list;
+static format_t *formatter_end;
+
+static uint64_t hrtime_delta(hrtime_t, hrtime_t);
+static u_longlong_t ull_delta(u_longlong_t, u_longlong_t);
+static uint_t u32_delta(uint_t, uint_t);
+static void setup(void (*nfunc)(void));
+static void print_timestamp(void);
+static void print_tty_hdr1(void);
+static void print_tty_hdr2(void);
+static void print_cpu_hdr1(void);
+static void print_cpu_hdr2(void);
+static void print_tty_data(void);
+static void print_cpu_data(void);
+static void print_err_hdr(void);
+static void print_disk_header(void);
+static void hdrout(void);
+static void disk_errors(void);
+static void do_newline(void);
+static void push_out(const char *, ...);
+static void printhdr(int);
+static void printxhdr(void);
+static void usage(void);
+static void do_args(int, char **);
+static void do_format(void);
+static void set_timer(int);
+static void handle_sig(int);
+static void show_all_disks(void);
+static void show_first_disk(void);
+static void show_other_disks(void);
+static void show_disk_errors(void *, void *, void *);
+static void write_core_header(void);
+static int fzero(double value);
+static int safe_strtoi(char const *val, char *errmsg);
+
+int
+main(int argc, char **argv)
+{
+ enum snapshot_types types = SNAP_SYSTEM;
+ kstat_ctl_t *kc;
+ long hz;
+
+ do_args(argc, argv);
+ do_format();
+
+ /*
+ * iostat historically showed CPU changes, even though
+ * it doesn't provide much useful information
+ */
+ types |= SNAP_CPUS;
+
+ if (do_disk)
+ types |= SNAP_IODEVS;
+
+ if (do_disk && !do_partitions_only)
+ df.if_allowed_types |= IODEV_DISK;
+ if (do_disk & DISK_IOPATH) {
+ df.if_allowed_types |= IODEV_IOPATH;
+ types |= SNAP_IOPATHS;
+ }
+ if (do_disk & DISK_ERROR_MASK)
+ types |= SNAP_IODEV_ERRORS;
+ if (do_partitions || do_partitions_only)
+ df.if_allowed_types |= IODEV_PARTITION;
+ if (do_conversions)
+ types |= SNAP_IODEV_PRETTY;
+ if (do_controller) {
+ if (!(do_disk & PRINT_VERTICAL) ||
+ (do_disk & DISK_EXTENDED_ERRORS))
+ fail(0, "-C can only be used with -e or -x.");
+ types |= SNAP_CONTROLLERS;
+ df.if_allowed_types |= IODEV_CONTROLLER;
+ }
+
+ hz = sysconf(_SC_CLK_TCK);
+
+ /*
+ * Undocumented behavior - sending a SIGCONT will result
+ * in a new header being emitted. Used only if we're not
+ * doing extended headers. This is a historical
+ * artifact.
+ */
+ if (!(do_disk & PRINT_VERTICAL))
+ (void) signal(SIGCONT, printhdr);
+
+ if (interval)
+ set_timer(interval);
+
+ kc = open_kstat();
+ newss = acquire_snapshot(kc, types, &df);
+
+ do {
+ if (do_tty || do_cpu) {
+ kstat_t *oldks;
+ oldks = oldss ? &oldss->s_sys.ss_agg_sys : NULL;
+ getime = cpu_ticks_delta(oldks,
+ &newss->s_sys.ss_agg_sys);
+ percent = (getime > 0.0) ? 100.0 / getime : 0.0;
+ getime = (getime / nr_active_cpus(newss)) / hz;
+ if (getime == 0.0)
+ getime = (double)interval;
+ if (getime == 0.0 || do_interval)
+ getime = 1.0;
+ }
+
+ if (formatter_list) {
+ format_t *tmp;
+ tmp = formatter_list;
+ while (tmp) {
+ (tmp->nfunc)();
+ tmp = tmp->next;
+ }
+ (void) fflush(stdout);
+ }
+
+ if (interval > 0 && iter != 1)
+ (void) pause();
+
+ free_snapshot(oldss);
+ oldss = newss;
+ newss = acquire_snapshot(kc, types, &df);
+
+ if (!suppress_state)
+ snapshot_report_changes(oldss, newss);
+
+ /* if config changed, show stats from boot */
+ if (snapshot_has_changed(oldss, newss)) {
+ free_snapshot(oldss);
+ oldss = NULL;
+ }
+
+ } while (--iter);
+
+ free_snapshot(oldss);
+ free_snapshot(newss);
+ return (0);
+}
+
+/*
+ * Some magic numbers used in header formatting.
+ *
+ * DISK_LEN = length of either "kps tps serv" or "wps rps util"
+ * using 0 as the first position
+ *
+ * DISK_ERROR_LEN = length of "s/w h/w trn tot" with one space on
+ * either side. Does not use zero as first pos.
+ *
+ * DEVICE_LEN = length of "device" + 1 character.
+ */
+
+#define DISK_LEN 11
+#define DISK_ERROR_LEN 16
+#define DEVICE_LEN 7
+
+/*ARGSUSED*/
+static void
+show_disk_name(void *v1, void *v2, void *data)
+{
+ struct iodev_snapshot *dev = (struct iodev_snapshot *)v2;
+ size_t slen;
+ char *name;
+ char fbuf[SMALL_SCRATCH_BUFLEN];
+
+ if (dev == NULL)
+ return;
+
+ name = dev->is_pretty ? dev->is_pretty : dev->is_name;
+ if (!do_raw) {
+ uint_t width;
+
+ slen = strlen(name);
+ /*
+ * The length is less
+ * than the section
+ * which will be displayed
+ * on the next line.
+ * Center the entry.
+ */
+
+ width = (DISK_LEN + 1)/2 + (slen / 2);
+ (void) snprintf(fbuf, sizeof (fbuf),
+ "%*s", width, name);
+ name = fbuf;
+ push_out("%-14.14s", name);
+ } else {
+ push_out(name);
+ }
+}
+
+/*ARGSUSED*/
+static void
+show_disk_header(void *v1, void *v2, void *data)
+{
+ push_out(disk_header);
+}
+
+/*
+ * Write out a two line header. What is written out depends on the flags
+ * selected but in the worst case consists of a tty header, a disk header
+ * providing information for 4 disks and a cpu header.
+ *
+ * The tty header consists of the word "tty" on the first line above the
+ * words "tin tout" on the next line. If present the tty portion consumes
+ * the first 10 characters of each line since "tin tout" is surrounded
+ * by single spaces.
+ *
+ * Each of the disk sections is a 14 character "block" in which the name of
+ * the disk is centered in the first 12 characters of the first line.
+ *
+ * The cpu section is an 11 character block with "cpu" centered over the
+ * section.
+ *
+ * The worst case should look as follows:
+ *
+ * 0---------1--------2---------3---------4---------5---------6---------7-------
+ * tty sd0 sd1 sd2 sd3 cpu
+ * tin tout kps tps serv kps tps serv kps tps serv kps tps serv us sy wt id
+ * NNN NNNN NNN NNN NNNN NNN NNN NNNN NNN NNN NNNN NNN NNN NNNN NN NN NN NN
+ *
+ * When -D is specified, the disk header looks as follows (worst case):
+ *
+ * 0---------1--------2---------3---------4---------5---------6---------7-------
+ * tty sd0 sd1 sd2 sd3 cpu
+ * tin tout rps wps util rps wps util rps wps util rps wps util us sy wt id
+ * NNN NNNN NNN NNN NNNN NNN NNN NNNN NNN NNN NNNN NNN NNN NNNN NN NN NN NN
+ */
+static void
+printhdr(int sig)
+{
+ /*
+ * If we're here because a signal fired, reenable the
+ * signal.
+ */
+ if (sig)
+ (void) signal(SIGCONT, printhdr);
+ /*
+ * Horizontal mode headers
+ *
+ * First line
+ */
+ if (do_tty)
+ print_tty_hdr1();
+
+ if (do_disk & DISK_NORMAL) {
+ (void) snapshot_walk(SNAP_IODEVS, NULL, newss,
+ show_disk_name, NULL);
+ }
+
+ if (do_cpu)
+ print_cpu_hdr1();
+ do_newline();
+
+ /*
+ * Second line
+ */
+ if (do_tty)
+ print_tty_hdr2();
+
+ if (do_disk & DISK_NORMAL) {
+ (void) snapshot_walk(SNAP_IODEVS, NULL, newss,
+ show_disk_header, NULL);
+ }
+
+ if (do_cpu)
+ print_cpu_hdr2();
+ do_newline();
+
+ tohdr = REPRINT;
+}
+
+/*
+ * Write out the extended header centered over the core information.
+ */
+static void
+write_core_header(void)
+{
+ char *edev = "extended device statistics";
+ uint_t lead_space_ct;
+ uint_t follow_space_ct;
+ size_t edevlen;
+
+ if (do_raw == 0) {
+ /*
+ * The things we do to look nice...
+ *
+ * Center the core output header. Make sure we have the
+ * right number of trailing spaces for follow-on headers
+ * (i.e., cpu and/or tty and/or errors).
+ */
+ edevlen = strlen(edev);
+ lead_space_ct = dh_len - edevlen;
+ lead_space_ct /= 2;
+ if (lead_space_ct > 0) {
+ follow_space_ct = dh_len - (lead_space_ct + edevlen);
+ if (do_disk & DISK_ERRORS)
+ follow_space_ct -= DISK_ERROR_LEN;
+ if ((do_disk & DISK_EXTENDED) && do_conversions)
+ follow_space_ct -= DEVICE_LEN;
+
+ push_out("%1$*2$.*2$s%3$s%4$*5$.*5$s", one_blank,
+ lead_space_ct, edev, one_blank, follow_space_ct);
+ } else
+ push_out("%56s", edev);
+ } else
+ push_out(edev);
+}
+
+/*
+ * In extended mode headers, we don't want to reprint the header on
+ * signals as they are printed every time anyways.
+ */
+static void
+printxhdr(void)
+{
+
+ /*
+ * Vertical mode headers
+ */
+ if (do_disk & DISK_EXTENDED)
+ setup(write_core_header);
+ if (do_disk & DISK_ERRORS)
+ setup(print_err_hdr);
+
+ if (do_conversions) {
+ setup(do_newline);
+ if (do_disk & (DISK_EXTENDED | DISK_ERRORS))
+ setup(print_disk_header);
+ setup(do_newline);
+ } else {
+ if (do_tty)
+ setup(print_tty_hdr1);
+ if (do_cpu)
+ setup(print_cpu_hdr1);
+ setup(do_newline);
+
+ if (do_disk & (DISK_EXTENDED | DISK_ERRORS))
+ setup(print_disk_header);
+ if (do_tty)
+ setup(print_tty_hdr2);
+ if (do_cpu)
+ setup(print_cpu_hdr2);
+ setup(do_newline);
+ }
+}
+
+/*
+ * Write out a line for this disk - note that show_disk writes out
+ * full lines or blocks for each selected disk.
+ */
+static void
+show_disk(void *v1, void *v2, void *data)
+{
+ struct iodev_snapshot *old = (struct iodev_snapshot *)v1;
+ struct iodev_snapshot *new = (struct iodev_snapshot *)v2;
+ int *count = (int *)data;
+ double rps, wps, tps, mtps, krps, kwps, kps, avw, avr, w_pct, r_pct;
+ double wserv, rserv, serv;
+ double iosize; /* kb/sec or MB/sec */
+ double etime, hr_etime;
+ char *disk_name;
+ u_longlong_t ldeltas;
+ uint_t udeltas;
+ uint64_t t_delta;
+ uint64_t w_delta;
+ uint64_t r_delta;
+ int doit = 1;
+ int i;
+ uint_t toterrs;
+ char *fstr;
+
+ if (new == NULL)
+ return;
+
+ switch (show_disk_mode) {
+ case SHOW_FIRST_ONLY:
+ if (count != NULL && *count)
+ return;
+ break;
+
+ case SHOW_SECOND_ONWARDS:
+ if (count != NULL && !*count) {
+ (*count)++;
+ return;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ disk_name = new->is_pretty ? new->is_pretty : new->is_name;
+
+ /*
+ * Only do if we want IO stats - Avoids errors traveling this
+ * section if that's all we want to see.
+ */
+ if (do_disk & DISK_IO_MASK) {
+ if (old) {
+ t_delta = hrtime_delta(old->is_snaptime,
+ new->is_snaptime);
+ } else {
+ t_delta = hrtime_delta(new->is_crtime,
+ new->is_snaptime);
+ }
+
+ if (new->is_type == IODEV_CONTROLLER && new->is_nr_children)
+ t_delta /= new->is_nr_children;
+
+ hr_etime = (double)t_delta;
+ if (hr_etime == 0.0)
+ hr_etime = (double)NANOSEC;
+ etime = hr_etime / (double)NANOSEC;
+
+ /* reads per second */
+ udeltas = u32_delta(old ? old->is_stats.reads : 0,
+ new->is_stats.reads);
+ rps = (double)udeltas;
+ rps /= etime;
+
+ /* writes per second */
+ udeltas = u32_delta(old ? old->is_stats.writes : 0,
+ new->is_stats.writes);
+ wps = (double)udeltas;
+ wps /= etime;
+
+ tps = rps + wps;
+ /* transactions per second */
+
+ /*
+ * report throughput as either kb/sec or MB/sec
+ */
+
+ if (!do_megabytes)
+ iosize = 1024.0;
+ else
+ iosize = 1048576.0;
+
+ ldeltas = ull_delta(old ? old->is_stats.nread : 0,
+ new->is_stats.nread);
+ if (ldeltas) {
+ krps = (double)ldeltas;
+ krps /= etime;
+ krps /= iosize;
+ } else
+ krps = 0.0;
+
+ ldeltas = ull_delta(old ? old->is_stats.nwritten : 0,
+ new->is_stats.nwritten);
+ if (ldeltas) {
+ kwps = (double)ldeltas;
+ kwps /= etime;
+ kwps /= iosize;
+ } else
+ kwps = 0.0;
+
+ /*
+ * Blocks transferred per second
+ */
+ kps = krps + kwps;
+
+ /*
+ * Average number of write transactions waiting
+ */
+ w_delta = hrtime_delta((u_longlong_t)
+ (old ? old->is_stats.wlentime : 0),
+ new->is_stats.wlentime);
+ if (w_delta) {
+ avw = (double)w_delta;
+ avw /= hr_etime;
+ } else
+ avw = 0.0;
+
+ /*
+ * Average number of read transactions waiting
+ */
+ r_delta = hrtime_delta(old ? old->is_stats.rlentime : 0,
+ new->is_stats.rlentime);
+ if (r_delta) {
+ avr = (double)r_delta;
+ avr /= hr_etime;
+ } else
+ avr = 0.0;
+
+ /*
+ * Average wait service time in milliseconds
+ */
+ if (tps > 0.0 && (avw != 0.0 || avr != 0.0)) {
+ mtps = 1000.0 / tps;
+ if (avw != 0.0)
+ wserv = avw * mtps;
+ else
+ wserv = 0.0;
+
+ if (avr != 0.0)
+ rserv = avr * mtps;
+ else
+ rserv = 0.0;
+ serv = rserv + wserv;
+ } else {
+ rserv = 0.0;
+ wserv = 0.0;
+ serv = 0.0;
+ }
+
+ /* % of time there is a transaction waiting for service */
+ t_delta = hrtime_delta(old ? old->is_stats.wtime : 0,
+ new->is_stats.wtime);
+ if (t_delta) {
+ w_pct = (double)t_delta;
+ w_pct /= hr_etime;
+ w_pct *= 100.0;
+
+ /*
+ * Average the wait queue utilization over the
+ * the controller's devices, if this is a controller.
+ */
+ if (new->is_type == IODEV_CONTROLLER)
+ w_pct /= new->is_nr_children;
+ } else
+ w_pct = 0.0;
+
+ /* % of time there is a transaction running */
+ t_delta = hrtime_delta(old ? old->is_stats.rtime : 0,
+ new->is_stats.rtime);
+ if (t_delta) {
+ r_pct = (double)t_delta;
+ r_pct /= hr_etime;
+ r_pct *= 100.0;
+
+ /*
+ * Average the percent busy over the controller's
+ * devices, if this is a controller.
+ */
+ if (new->is_type == IODEV_CONTROLLER)
+ w_pct /= new->is_nr_children;
+ } else {
+ r_pct = 0.0;
+ }
+
+ /* % of time there is a transaction running */
+ if (do_interval) {
+ rps *= etime;
+ wps *= etime;
+ tps *= etime;
+ krps *= etime;
+ kwps *= etime;
+ kps *= etime;
+ }
+ }
+
+ if (do_disk & (DISK_EXTENDED | DISK_ERRORS)) {
+ if ((!do_conversions) && ((suppress_zero == 0) ||
+ ((do_disk & DISK_EXTENDED) == 0))) {
+ if (do_raw == 0)
+ push_out(DISPLAYED_NAME_FORMAT,
+ disk_name);
+ else
+ push_out(disk_name);
+ }
+ }
+
+ switch (do_disk & DISK_IO_MASK) {
+ case DISK_OLD:
+ if (do_raw == 0)
+ fstr = "%3.0f %3.0f %4.0f ";
+ else
+ fstr = "%.0f,%.0f,%.0f";
+ push_out(fstr, kps, tps, serv);
+ break;
+ case DISK_NEW:
+ if (do_raw == 0)
+ fstr = "%3.0f %3.0f %4.1f ";
+ else
+ fstr = "%.0f,%.0f,%.1f";
+ push_out(fstr, rps, wps, r_pct);
+ break;
+ case DISK_EXTENDED:
+ if (suppress_zero) {
+ if (fzero(rps) && fzero(wps) && fzero(krps) &&
+ fzero(kwps) && fzero(avw) && fzero(avr) &&
+ fzero(serv) && fzero(w_pct) && fzero(r_pct))
+ doit = 0;
+ else if (do_conversions == 0) {
+ if (do_raw == 0)
+ push_out(DISPLAYED_NAME_FORMAT,
+ disk_name);
+ else
+ push_out(disk_name);
+ }
+ }
+ if (doit) {
+ if (!do_conversions) {
+ if (do_raw == 0) {
+ fstr = " %6.1f %6.1f %6.1f %6.1f "
+ "%4.1f %4.1f %6.1f %3.0f "
+ "%3.0f ";
+ } else {
+ fstr = "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,"
+ "%.1f,%.0f,%.0f";
+ }
+ push_out(fstr, rps, wps, krps, kwps, avw, avr,
+ serv, w_pct, r_pct);
+ } else {
+ if (do_raw == 0) {
+ fstr = " %6.1f %6.1f %6.1f %6.1f "
+ "%4.1f %4.1f %6.1f %6.1f "
+ "%3.0f %3.0f ";
+ } else {
+ fstr = "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,"
+ "%.1f,%.1f,%.0f,%.0f";
+ }
+ push_out(fstr, rps, wps, krps, kwps, avw, avr,
+ wserv, rserv, w_pct, r_pct);
+ }
+ }
+ break;
+ }
+
+ if (do_disk & DISK_ERRORS) {
+ if ((do_disk == DISK_ERRORS)) {
+ if (do_raw == 0)
+ push_out(two_blanks);
+ }
+
+ if (new->is_errors.ks_data) {
+ kstat_named_t *knp;
+ char *efstr;
+
+ if (do_raw == 0)
+ efstr = "%3u ";
+ else
+ efstr = "%u";
+ toterrs = 0;
+ knp = KSTAT_NAMED_PTR(&new->is_errors);
+ for (i = 0; i < 3; i++) {
+ switch (knp[i].data_type) {
+ case KSTAT_DATA_ULONG:
+ push_out(efstr,
+ knp[i].value.ui32);
+ toterrs += knp[i].value.ui32;
+ break;
+ case KSTAT_DATA_ULONGLONG:
+ /*
+ * We're only set up to
+ * write out the low
+ * order 32-bits so
+ * just grab that.
+ */
+ push_out(efstr,
+ knp[i].value.ui32);
+ toterrs += knp[i].value.ui32;
+ break;
+ default:
+ break;
+ }
+ }
+ push_out(efstr, toterrs);
+ } else {
+ if (do_raw == 0)
+ push_out(" 0 0 0 0 ");
+ else
+ push_out("0,0,0,0");
+ }
+
+ }
+
+ if (suppress_zero == 0 || doit == 1) {
+ if ((do_disk & (DISK_EXTENDED | DISK_ERRORS)) &&
+ do_conversions) {
+ push_out("%s", disk_name);
+ if (show_mountpts && new->is_dname) {
+ mnt_t *mount_pt;
+ char *lu;
+ char lub[SMALL_SCRATCH_BUFLEN];
+
+ lu = strrchr(new->is_dname, '/');
+ if (lu) {
+ if (strcmp(disk_name, lu) == 0)
+ lu = new->is_dname;
+ else {
+ *lu = 0;
+ (void) strcpy(lub,
+ new->is_dname);
+ *lu = '/';
+ (void) strcat(lub, "/");
+ (void) strcat(lub,
+ disk_name);
+ lu = lub;
+ }
+ } else
+ lu = disk_name;
+ mount_pt = lookup_mntent_byname(lu);
+ if (mount_pt) {
+ if (do_raw == 0)
+ push_out(" (%s)",
+ mount_pt->mount_point);
+ else
+ push_out("(%s)",
+ mount_pt->mount_point);
+ }
+ }
+ }
+ }
+
+ if ((do_disk & PRINT_VERTICAL) && show_disk_mode != SHOW_FIRST_ONLY)
+ do_newline();
+
+ if (count != NULL)
+ (*count)++;
+}
+
+static void
+usage(void)
+{
+ (void) fprintf(stderr,
+ "Usage: iostat [-cCdDeEiImMnpPrstxXz] "
+ " [-l n] [-T d|u] [disk ...] [interval [count]]\n"
+ "\t\t-c: report percentage of time system has spent\n"
+ "\t\t\tin user/system/wait/idle mode\n"
+ "\t\t-C: report disk statistics by controller\n"
+ "\t\t-d: display disk Kb/sec, transfers/sec, avg. \n"
+ "\t\t\tservice time in milliseconds \n"
+ "\t\t-D: display disk reads/sec, writes/sec, \n"
+ "\t\t\tpercentage disk utilization \n"
+ "\t\t-e: report device error summary statistics\n"
+ "\t\t-E: report extended device error statistics\n"
+ "\t\t-i: show device IDs for -E output\n"
+ "\t\t-I: report the counts in each interval,\n"
+ "\t\t\tinstead of rates, where applicable\n"
+ "\t\t-l n: Limit the number of disks to n\n"
+ "\t\t-m: Display mount points (most useful with -p)\n"
+ "\t\t-M: Display data throughput in MB/sec "
+ "instead of Kb/sec\n"
+ "\t\t-n: convert device names to cXdYtZ format\n"
+ "\t\t-p: report per-partition disk statistics\n"
+ "\t\t-P: report per-partition disk statistics only,\n"
+ "\t\t\tno per-device disk statistics\n"
+ "\t\t-r: Display data in comma separated format\n"
+ "\t\t-s: Suppress state change messages\n"
+ "\t\t-T d|u Display a timestamp in date (d) or unix "
+ "time_t (u)\n"
+ "\t\t-t: display chars read/written to terminals\n"
+ "\t\t-x: display extended disk statistics\n"
+ "\t\t-X: display I/O path statistics\n"
+ "\t\t-z: Suppress entries with all zero values\n");
+ exit(1);
+}
+
+/*ARGSUSED*/
+static void
+show_disk_errors(void *v1, void *v2, void *d)
+{
+ struct iodev_snapshot *disk = (struct iodev_snapshot *)v2;
+ kstat_named_t *knp;
+ size_t col;
+ int i, len;
+ char *dev_name = disk->is_name;
+
+ if (disk->is_errors.ks_ndata == 0)
+ return;
+ if (disk->is_type == IODEV_CONTROLLER)
+ return;
+
+ if (disk->is_pretty)
+ dev_name = disk->is_pretty;
+
+ len = strlen(dev_name);
+ if (len > 20)
+ push_out("%s ", dev_name);
+ else if (len > 16)
+ push_out("%-20.20s ", dev_name);
+ else {
+ if (do_conversions)
+ push_out("%-16.16s ", dev_name);
+ else
+ push_out("%-9.9s ", dev_name);
+ }
+ col = 0;
+
+ knp = KSTAT_NAMED_PTR(&disk->is_errors);
+ for (i = 0; i < disk->is_errors.ks_ndata; i++) {
+ /* skip kstats that the driver did not kstat_named_init */
+ if (knp[i].name[0] == 0)
+ continue;
+
+ col += strlen(knp[i].name);
+
+ switch (knp[i].data_type) {
+ case KSTAT_DATA_CHAR:
+ if ((strcmp(knp[i].name, "Serial No") == 0) &&
+ do_devid) {
+ if (disk->is_devid) {
+ push_out("Device Id: %s ",
+ disk->is_devid);
+ col += strlen(disk->is_devid);
+ } else
+ push_out("Device Id: ");
+ } else {
+ push_out("%s: %-.16s ", knp[i].name,
+ &knp[i].value.c[0]);
+ col += strlen(&knp[i].value.c[0]);
+ }
+ break;
+ case KSTAT_DATA_ULONG:
+ push_out("%s: %u ", knp[i].name,
+ knp[i].value.ui32);
+ col += 4;
+ break;
+ case KSTAT_DATA_ULONGLONG:
+ if (strcmp(knp[i].name, "Size") == 0) {
+ push_out("%s: %2.2fGB <%llu bytes>\n",
+ knp[i].name,
+ (float)knp[i].value.ui64 /
+ DISK_GIGABYTE,
+ knp[i].value.ui64);
+ col = 0;
+ break;
+ }
+ push_out("%s: %u ", knp[i].name,
+ knp[i].value.ui32);
+ col += 4;
+ break;
+ }
+ if ((col >= 62) || (i == 2)) {
+ do_newline();
+ col = 0;
+ }
+ }
+ if (col > 0) {
+ do_newline();
+ }
+ do_newline();
+}
+
+void
+do_args(int argc, char **argv)
+{
+ int c;
+ int errflg = 0;
+ extern char *optarg;
+ extern int optind;
+
+ while ((c = getopt(argc, argv, "tdDxXCciIpPnmMeEszrT:l:")) != EOF)
+ switch (c) {
+ case 't':
+ do_tty++;
+ break;
+ case 'd':
+ do_disk |= DISK_OLD;
+ break;
+ case 'D':
+ do_disk |= DISK_NEW;
+ break;
+ case 'x':
+ do_disk |= DISK_EXTENDED;
+ break;
+ case 'X':
+ do_disk |= DISK_IOPATH;
+ break;
+ case 'C':
+ do_controller++;
+ break;
+ case 'c':
+ do_cpu++;
+ break;
+ case 'I':
+ do_interval++;
+ break;
+ case 'p':
+ do_partitions++;
+ break;
+ case 'P':
+ do_partitions_only++;
+ break;
+ case 'n':
+ do_conversions++;
+ break;
+ case 'M':
+ do_megabytes++;
+ break;
+ case 'e':
+ do_disk |= DISK_ERRORS;
+ break;
+ case 'E':
+ do_disk |= DISK_EXTENDED_ERRORS;
+ break;
+ case 'i':
+ do_devid = 1;
+ break;
+ case 's':
+ suppress_state = 1;
+ break;
+ case 'z':
+ suppress_zero = 1;
+ break;
+ case 'm':
+ show_mountpts = 1;
+ break;
+ case 'T':
+ if (optarg) {
+ if (*optarg == 'u')
+ do_timestamp = UDATE;
+ else if (*optarg == 'd')
+ do_timestamp = CDATE;
+ else
+ errflg++;
+ } else
+ errflg++;
+ break;
+ case 'r':
+ do_raw = 1;
+ break;
+ case 'l':
+ df.if_max_iodevs = safe_strtoi(optarg, "invalid limit");
+ if (df.if_max_iodevs < 1)
+ usage();
+ break;
+ case '?':
+ errflg++;
+ }
+
+ if ((do_disk & DISK_OLD) && (do_disk & DISK_NEW)) {
+ (void) fprintf(stderr, "-d and -D are incompatible.\n");
+ usage();
+ }
+
+ if (errflg) {
+ usage();
+ }
+ /* if no output classes explicity specified, use defaults */
+ if (do_tty == 0 && do_disk == 0 && do_cpu == 0)
+ do_tty = do_cpu = 1, do_disk = DISK_OLD;
+
+ /*
+ * If conflicting options take the preferred
+ * -D and -x result in -x
+ * -d or -D and -e or -E gives only whatever -d or -D was specified
+ */
+ if ((do_disk & DISK_EXTENDED) && (do_disk & DISK_NORMAL))
+ do_disk &= ~DISK_NORMAL;
+ if ((do_disk & DISK_NORMAL) && (do_disk & DISK_ERROR_MASK))
+ do_disk &= ~DISK_ERROR_MASK;
+
+ /*
+ * I/O path stats are only available with extended (-x) stats
+ */
+ if ((do_disk & DISK_IOPATH) && !(do_disk & DISK_EXTENDED))
+ do_disk &= ~DISK_IOPATH;
+
+ /* nfs, tape, always shown */
+ df.if_allowed_types = IODEV_NFS | IODEV_TAPE;
+
+ /*
+ * If limit == 0 then no command line limit was set, else if any of
+ * the flags that cause unlimited disks were not set,
+ * use the default of 4
+ */
+ if (df.if_max_iodevs == 0) {
+ df.if_max_iodevs = DEFAULT_LIMIT;
+ df.if_skip_floppy = 1;
+ if (do_disk & (DISK_EXTENDED | DISK_ERRORS |
+ DISK_EXTENDED_ERRORS)) {
+ df.if_max_iodevs = UNLIMITED_IODEVS;
+ df.if_skip_floppy = 0;
+ }
+ }
+ if (do_disk) {
+ size_t count = 0;
+ size_t i = optind;
+
+ while (i < argc && !isdigit(argv[i][0])) {
+ count++;
+ i++;
+ }
+
+ /*
+ * "Note: disks explicitly requested
+ * are not subject to this disk limit"
+ */
+ if (count > df.if_max_iodevs)
+ df.if_max_iodevs = count;
+ df.if_names = safe_alloc(count * sizeof (char *));
+ (void) memset(df.if_names, 0, count * sizeof (char *));
+
+ while (optind < argc && !isdigit(argv[optind][0]))
+ df.if_names[df.if_nr_names++] = argv[optind++];
+ }
+ if (optind < argc) {
+ interval = safe_strtoi(argv[optind], "invalid interval");
+ if (interval < 1)
+ fail(0, "invalid interval");
+ optind++;
+
+ if (optind < argc) {
+ iter = safe_strtoi(argv[optind], "invalid count");
+ if (iter < 1)
+ fail(0, "invalid count");
+ optind++;
+ }
+ }
+ if (interval == 0)
+ iter = 1;
+ if (optind < argc)
+ usage();
+}
+
+/*
+ * Driver for doing the extended header formatting. Will produce
+ * the function stack needed to output an extended header based
+ * on the options selected.
+ */
+
+void
+do_format(void)
+{
+ char header[SMALL_SCRATCH_BUFLEN];
+ char ch;
+ char iosz;
+ const char *fstr;
+
+ disk_header[0] = 0;
+ ch = (do_interval ? 'i' : 's');
+ iosz = (do_megabytes ? 'M' : 'k');
+ if (do_disk & DISK_ERRORS) {
+ if (do_raw == 0) {
+ (void) sprintf(header, "s/w h/w trn tot ");
+ } else
+ (void) sprintf(header, "s/w,h/w,trn,tot");
+ } else
+ *header = NULL;
+ switch (do_disk & DISK_IO_MASK) {
+ case DISK_OLD:
+ if (do_raw == 0)
+ fstr = "%cp%c tp%c serv ";
+ else
+ fstr = "%cp%c,tp%c,serv";
+ (void) snprintf(disk_header, sizeof (disk_header),
+ fstr, iosz, ch, ch);
+ break;
+ case DISK_NEW:
+ if (do_raw == 0)
+ fstr = "rp%c wp%c util ";
+ else
+ fstr = "%rp%c,wp%c,util";
+ (void) snprintf(disk_header, sizeof (disk_header),
+ fstr, ch, ch);
+ break;
+ case DISK_EXTENDED:
+ if (!do_conversions) {
+ if (do_raw == 0)
+ fstr = "device r/%c w/%c "
+ "%cr/%c %cw/%c wait actv "
+ "svc_t %%%%w %%%%b %s";
+ else
+ fstr = "device,r/%c,w/%c,%cr/%c,%cw/%c,"
+ "wait,actv,svc_t,%%%%w,"
+ "%%%%b,%s";
+ (void) snprintf(disk_header,
+ sizeof (disk_header),
+ fstr, ch, ch, iosz, ch, iosz,
+ ch, header);
+ } else {
+ if (do_raw == 0) {
+ fstr = " r/%c w/%c %cr/%c "
+ "%cw/%c wait actv wsvc_t asvc_t "
+ "%%%%w %%%%b %sdevice";
+ } else {
+ fstr = "r/%c,w/%c,%cr/%c,%cw/%c,"
+ "wait,actv,wsvc_t,asvc_t,"
+ "%%%%w,%%%%b,%sdevice";
+ }
+ (void) snprintf(disk_header,
+ sizeof (disk_header),
+ fstr, ch, ch, iosz, ch, iosz,
+ ch, header);
+ }
+ break;
+ default:
+ break;
+ }
+ if (do_disk == DISK_ERRORS) {
+ char *sep;
+
+ if (!do_conversions) {
+ if (do_raw == 0) {
+ sep = " ";
+ } else
+ sep = ",";
+ (void) snprintf(disk_header, sizeof (disk_header),
+ "%s%s%s", "device", sep, header);
+ } else {
+ if (do_raw == 0) {
+ (void) snprintf(disk_header,
+ sizeof (disk_header),
+ " %s", header);
+ } else
+ (void) strcpy(disk_header, header);
+ }
+ } else {
+ /*
+ * Need to subtract two characters for the % escape in
+ * the string.
+ */
+ dh_len = strlen(disk_header) - 2;
+ }
+
+ if (do_timestamp)
+ setup(print_timestamp);
+
+ /*
+ * -n *and* (-E *or* -e *or* -x)
+ */
+ if (do_conversions && (do_disk & PRINT_VERTICAL)) {
+ if (do_tty)
+ setup(print_tty_hdr1);
+ if (do_cpu)
+ setup(print_cpu_hdr1);
+ if (do_tty || do_cpu)
+ setup(do_newline);
+ if (do_tty)
+ setup(print_tty_hdr2);
+ if (do_cpu)
+ setup(print_cpu_hdr2);
+ if (do_tty || do_cpu)
+ setup(do_newline);
+ if (do_tty)
+ setup(print_tty_data);
+ if (do_cpu)
+ setup(print_cpu_data);
+ if (do_tty || do_cpu)
+ setup(do_newline);
+ printxhdr();
+
+ setup(show_all_disks);
+ } else {
+ /*
+ * These unholy gymnastics are necessary to place CPU/tty
+ * data to the right of the disks/errors for the first
+ * line in vertical mode.
+ */
+ if (do_disk & PRINT_VERTICAL) {
+ printxhdr();
+
+ setup(show_first_disk);
+ if (do_tty)
+ setup(print_tty_data);
+ if (do_cpu)
+ setup(print_cpu_data);
+ setup(do_newline);
+
+ setup(show_other_disks);
+ } else {
+ setup(hdrout);
+ if (do_tty)
+ setup(print_tty_data);
+ setup(show_all_disks);
+ if (do_cpu)
+ setup(print_cpu_data);
+ }
+
+ setup(do_newline);
+ }
+ if (do_disk & DISK_EXTENDED_ERRORS)
+ setup(disk_errors);
+}
+
+/*
+ * Add a new function to the list of functions
+ * for this invocation. Once on the stack the
+ * function is never removed nor does its place
+ * change.
+ */
+void
+setup(void (*nfunc)(void))
+{
+ format_t *tmp;
+
+ tmp = safe_alloc(sizeof (format_t));
+ tmp->nfunc = nfunc;
+ tmp->next = 0;
+ if (formatter_end)
+ formatter_end->next = tmp;
+ else
+ formatter_list = tmp;
+ formatter_end = tmp;
+
+}
+
+/*
+ * The functions after this comment are devoted to printing
+ * various parts of the header. They are selected based on the
+ * options provided when the program was invoked. The functions
+ * are either directly invoked in printhdr() or are indirectly
+ * invoked by being placed on the list of functions used when
+ * extended headers are used.
+ */
+void
+print_tty_hdr1(void)
+{
+ char *fstr;
+ char *dstr;
+
+ if (do_raw == 0) {
+ fstr = "%10.10s";
+ dstr = "tty ";
+ } else {
+ fstr = "%s";
+ dstr = "tty";
+ }
+ push_out(fstr, dstr);
+}
+
+void
+print_tty_hdr2(void)
+{
+ if (do_raw == 0)
+ push_out("%-10.10s", " tin tout");
+ else
+ push_out("tin,tout");
+}
+
+void
+print_cpu_hdr1(void)
+{
+ char *dstr;
+
+ if (do_raw == 0)
+ dstr = " cpu";
+ else
+ dstr = "cpu";
+ push_out(dstr);
+}
+
+void
+print_cpu_hdr2(void)
+{
+ char *dstr;
+
+ if (do_raw == 0)
+ dstr = " us sy wt id";
+ else
+ dstr = "us,sy,wt,id";
+ push_out(dstr);
+}
+
+/*
+ * Assumption is that tty data is always first - no need for raw mode leading
+ * comma.
+ */
+void
+print_tty_data(void)
+{
+ char *fstr;
+ uint64_t deltas;
+ double raw;
+ double outch;
+ kstat_t *oldks = NULL;
+
+ if (oldss)
+ oldks = &oldss->s_sys.ss_agg_sys;
+
+ if (do_raw == 0)
+ fstr = " %3.0f %4.0f ";
+ else
+ fstr = "%.0f,%.0f";
+ deltas = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "rawch");
+ raw = deltas;
+ raw /= getime;
+ deltas = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "outch");
+ outch = deltas;
+ outch /= getime;
+ push_out(fstr, raw, outch);
+}
+
+/*
+ * Write out CPU data
+ */
+void
+print_cpu_data(void)
+{
+ char *fstr;
+ uint64_t idle;
+ uint64_t user;
+ uint64_t kern;
+ uint64_t wait;
+ kstat_t *oldks = NULL;
+
+ if (oldss)
+ oldks = &oldss->s_sys.ss_agg_sys;
+
+ if (do_raw == 0)
+ fstr = " %2.0f %2.0f %2.0f %2.0f";
+ else
+ fstr = "%.0f,%.0f,%.0f,%.0f";
+
+ idle = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "cpu_ticks_idle");
+ user = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "cpu_ticks_user");
+ kern = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "cpu_ticks_kernel");
+ wait = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "cpu_ticks_wait");
+ push_out(fstr, user * percent, kern * percent,
+ wait * percent, idle * percent);
+}
+
+/*
+ * Emit the appropriate header.
+ */
+void
+hdrout(void)
+{
+ if (do_raw == 0) {
+ if (--tohdr == 0)
+ printhdr(0);
+ } else if (hdr_out == 0) {
+ printhdr(0);
+ hdr_out = 1;
+ }
+}
+
+/*
+ * Write out disk errors when -E is specified.
+ */
+void
+disk_errors(void)
+{
+ (void) snapshot_walk(SNAP_IODEVS, oldss, newss, show_disk_errors, NULL);
+}
+
+void
+show_first_disk(void)
+{
+ int count = 0;
+
+ show_disk_mode = SHOW_FIRST_ONLY;
+
+ (void) snapshot_walk(SNAP_IODEVS, oldss, newss, show_disk, &count);
+}
+
+void
+show_other_disks(void)
+{
+ int count = 0;
+
+ show_disk_mode = SHOW_SECOND_ONWARDS;
+
+ (void) snapshot_walk(SNAP_IODEVS, oldss, newss, show_disk, &count);
+}
+
+void
+show_all_disks(void)
+{
+ int count = 0;
+
+ show_disk_mode = SHOW_ALL;
+
+ (void) snapshot_walk(SNAP_IODEVS, oldss, newss, show_disk, &count);
+}
+
+/*
+ * Write a newline out and clear the lineout flag.
+ */
+static void
+do_newline(void)
+{
+ if (lineout) {
+ (void) putchar('\n');
+ lineout = 0;
+ }
+}
+
+/*
+ * Generalized printf function that determines what extra
+ * to print out if we're in raw mode. At this time we
+ * don't care about errors.
+ */
+static void
+push_out(const char *message, ...)
+{
+ va_list args;
+
+ va_start(args, message);
+ if (do_raw && lineout == 1)
+ (void) putchar(',');
+ (void) vprintf(message, args);
+ va_end(args);
+ lineout = 1;
+}
+
+/*
+ * Emit the header string when -e is specified.
+ */
+static void
+print_err_hdr(void)
+{
+ char obuf[SMALL_SCRATCH_BUFLEN];
+
+ if (do_conversions == 0) {
+ if (!(do_disk & DISK_EXTENDED)) {
+ (void) snprintf(obuf, sizeof (obuf),
+ "%11s", one_blank);
+ push_out(obuf);
+ }
+ } else if (do_disk == DISK_ERRORS)
+ push_out(two_blanks);
+ else
+ push_out(one_blank);
+ push_out("---- errors --- ");
+}
+
+/*
+ * Emit the header string when -e is specified.
+ */
+static void
+print_disk_header(void)
+{
+ push_out(disk_header);
+}
+
+/*
+ * Write out a timestamp. Format is all that goes out on
+ * the line so no use of push_out.
+ *
+ * Write out as decimal reprentation of time_t value
+ * (-T u was specified) or the string returned from
+ * ctime() (-T d was specified).
+ */
+static void
+print_timestamp(void)
+{
+ time_t t;
+
+ if (time(&t) != -1) {
+ if (do_timestamp == UDATE) {
+ (void) printf("%ld\n", t);
+ } else if (do_timestamp == CDATE) {
+ char *cpt;
+
+ cpt = ctime(&t);
+ if (cpt) {
+ (void) fputs(cpt, stdout);
+ }
+ }
+ }
+}
+
+/*
+ * No, UINTMAX_MAX isn't the right thing here since
+ * it is #defined to be either INT32_MAX or INT64_MAX
+ * depending on the whether _LP64 is defined.
+ *
+ * We want to handle the odd future case of having
+ * ulonglong_t be more than 64 bits but we have
+ * no nice #define MAX value we can drop in place
+ * without having to change this code in the future.
+ */
+
+u_longlong_t
+ull_delta(u_longlong_t old, u_longlong_t new)
+{
+ if (new >= old)
+ return (new - old);
+ else
+ return ((UINT64_MAX - old) + new + 1);
+}
+
+/*
+ * Return the number of ticks delta between two hrtime_t
+ * values. Attempt to cater for various kinds of overflow
+ * in hrtime_t - no matter how improbable.
+ */
+uint64_t
+hrtime_delta(hrtime_t old, hrtime_t new)
+{
+ uint64_t del;
+
+ if ((new >= old) && (old >= 0L))
+ return (new - old);
+ else {
+ /*
+ * We've overflowed the positive portion of an
+ * hrtime_t.
+ */
+ if (new < 0L) {
+ /*
+ * The new value is negative. Handle the
+ * case where the old value is positive or
+ * negative.
+ */
+ uint64_t n1;
+ uint64_t o1;
+
+ n1 = -new;
+ if (old > 0L)
+ return (n1 - old);
+ else {
+ o1 = -old;
+ del = n1 - o1;
+ return (del);
+ }
+ } else {
+ /*
+ * Either we've just gone from being negative
+ * to positive *or* the last entry was positive
+ * and the new entry is also positive but *less*
+ * than the old entry. This implies we waited
+ * quite a few days on a very fast system between
+ * iostat displays.
+ */
+ if (old < 0L) {
+ uint64_t o2;
+
+ o2 = -old;
+ del = UINT64_MAX - o2;
+ } else {
+ del = UINT64_MAX - old;
+ }
+ del += new;
+ return (del);
+ }
+ }
+}
+
+/*
+ * Take the difference of an unsigned 32
+ * bit int attempting to cater for
+ * overflow.
+ */
+uint_t
+u32_delta(uint_t old, uint_t new)
+{
+ if (new >= old)
+ return (new - old);
+ else
+ return ((UINT32_MAX - old) + new + 1);
+}
+
+/*
+ * Create and arm the timer. Used only when an interval has been specified.
+ * Used in lieu of poll to ensure that we provide info for exactly the
+ * desired period.
+ */
+void
+set_timer(int interval)
+{
+ timer_t t_id;
+ itimerspec_t time_struct;
+ struct sigevent sig_struct;
+ struct sigaction act;
+
+ bzero(&sig_struct, sizeof (struct sigevent));
+ bzero(&act, sizeof (struct sigaction));
+
+ /* Create timer */
+ sig_struct.sigev_notify = SIGEV_SIGNAL;
+ sig_struct.sigev_signo = SIGUSR1;
+ sig_struct.sigev_value.sival_int = 0;
+
+ if (timer_create(CLOCK_REALTIME, &sig_struct, &t_id) != 0) {
+ fail(1, "Timer creation failed");
+ }
+
+ act.sa_handler = handle_sig;
+
+ if (sigaction(SIGUSR1, &act, NULL) != 0) {
+ fail(1, "Could not set up signal handler");
+ }
+
+ time_struct.it_value.tv_sec = interval;
+ time_struct.it_value.tv_nsec = 0;
+ time_struct.it_interval.tv_sec = interval;
+ time_struct.it_interval.tv_nsec = 0;
+
+ /* Arm timer */
+ if ((timer_settime(t_id, 0, &time_struct, NULL)) != 0) {
+ fail(1, "Setting timer failed");
+ }
+}
+/* ARGSUSED */
+void
+handle_sig(int x)
+{
+}
+
+/*
+ * This is exactly what is needed for standard iostat output,
+ * but make sure to use it only for that
+ */
+#define EPSILON (0.1)
+static int
+fzero(double value)
+{
+ return (value >= 0.0 && value < EPSILON);
+}
+
+static int
+safe_strtoi(char const *val, char *errmsg)
+{
+ char *end;
+ long tmp;
+
+ errno = 0;
+ tmp = strtol(val, &end, 10);
+ if (*end != '\0' || errno)
+ fail(0, "%s %s", errmsg, val);
+ return ((int)tmp);
+}
diff --git a/usr/src/cmd/stat/mpstat/Makefile b/usr/src/cmd/stat/mpstat/Makefile
new file mode 100644
index 0000000000..476be74175
--- /dev/null
+++ b/usr/src/cmd/stat/mpstat/Makefile
@@ -0,0 +1,62 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (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 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+PROG = mpstat
+OBJS = mpstat.o
+SRCS =$(OBJS:%.o=%.c) $(COMMON_SRCS)
+
+include $(SRC)/cmd/Makefile.cmd
+include $(SRC)/cmd/stat/Makefile.stat
+
+LDLIBS += -ldevinfo -lkstat
+CFLAGS += $(CCVERBOSE) -I${STATCOMMONDIR}
+FILEMODE= 0555
+GROUP= bin
+
+lint := LINTFLAGS = -muxs -I$(STATCOMMONDIR)
+
+.KEEP_STATE:
+
+all: $(PROG)
+
+install: all $(ROOTPROG)
+
+$(PROG): $(OBJS) $(COMMON_OBJS)
+ $(LINK.c) -o $(PROG) $(OBJS) $(COMMON_OBJS) $(LDLIBS)
+ $(POST_PROCESS)
+
+%.o : $(STATCOMMONDIR)/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
+
+clean:
+ -$(RM) $(OBJS) $(COMMON_OBJS)
+
+lint: lint_SRCS
+
+include $(SRC)/cmd/Makefile.targ
diff --git a/usr/src/cmd/stat/mpstat/mpstat.c b/usr/src/cmd/stat/mpstat/mpstat.c
new file mode 100644
index 0000000000..b2c1793a8b
--- /dev/null
+++ b/usr/src/cmd/stat/mpstat/mpstat.c
@@ -0,0 +1,488 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/pset.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/sysinfo.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <memory.h>
+#include <string.h>
+#include <strings.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <kstat.h>
+#include <poll.h>
+
+#include "statcommon.h"
+
+#define SNAP(s, i, l, n) ((s) ? agg_proc_snap(s, i, l, n) : 0)
+
+#define REPRINT 20
+
+char cmdname[] = "mpstat";
+
+static int hz;
+static int display_pset = -1;
+static int show_set = 0;
+static int suppress_state;
+
+static void print_header(int, int);
+static void show_cpu_usage(struct snapshot *, struct snapshot *, int);
+static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int display_agg = 0;
+ int iter = 1;
+ int interval = 0;
+ int poll_interval = 0;
+ char *endptr;
+ int infinite_cycles = 0;
+ kstat_ctl_t *kc;
+ struct snapshot *old = NULL;
+ struct snapshot *new = NULL;
+ enum snapshot_types types = SNAP_CPUS;
+
+ while ((c = getopt(argc, argv, "apP:q")) != (int)EOF)
+ switch (c) {
+ case 'a':
+ /*
+ * Display aggregate data for processor sets.
+ */
+ display_agg = 1;
+ break;
+ case 'p':
+ /*
+ * Display all processor sets.
+ */
+ if (display_pset != -1)
+ usage();
+ show_set = 1;
+ break;
+ case 'P':
+ /*
+ * Display specific processor set.
+ */
+ if (show_set == 1)
+ usage();
+ display_pset = (int)strtol
+ (optarg, &endptr, 10);
+ if (*endptr != NULL)
+ usage();
+ /*
+ * Not valid to specify a negative processor
+ * set value.
+ */
+ if (display_pset < 0)
+ usage();
+ break;
+ case 'q':
+ suppress_state = 1;
+ break;
+ case '?':
+ usage();
+ break;
+ }
+
+ hz = sysconf(_SC_CLK_TCK);
+
+ if (argc > optind) {
+ interval = (int)strtol(argv[optind], &endptr, 10);
+ if (*endptr != NULL)
+ usage();
+ poll_interval = 1000 * interval;
+ if (argc > optind + 1) {
+ iter = (unsigned int)strtoul
+ (argv[optind + 1], &endptr, 10);
+ if (*endptr != NULL || iter < 0)
+ usage();
+ if (iter == 0)
+ return (0);
+ } else {
+ infinite_cycles = 1;
+ }
+ }
+
+ if (display_agg || show_set || display_pset != -1)
+ types |= SNAP_PSETS;
+
+ kc = open_kstat();
+
+ while (infinite_cycles || iter > 0) {
+ free_snapshot(old);
+ old = new;
+ new = acquire_snapshot(kc, types, NULL);
+
+ if (!suppress_state)
+ snapshot_report_changes(old, new);
+
+ /* if config changed, show stats from boot */
+ if (snapshot_has_changed(old, new)) {
+ free_snapshot(old);
+ old = NULL;
+ }
+
+ show_cpu_usage(old, new, display_agg);
+
+ if (!infinite_cycles && --iter < 1)
+ break;
+
+ (void) poll(NULL, 0, poll_interval);
+ }
+ return (0);
+}
+
+/*
+ * Print an mpstat output header.
+ */
+static void
+print_header(int display_agg, int show_set)
+{
+ if (display_agg == 1)
+ (void) printf("SET minf mjf xcal intr ithr csw icsw migr "
+ "smtx srw syscl usr sys wt idl sze");
+ else {
+ (void) printf("CPU minf mjf xcal intr ithr csw icsw migr "
+ "smtx srw syscl usr sys wt idl");
+ if (show_set == 1)
+ (void) printf(" set");
+ }
+ (void) printf("\n");
+}
+
+static void
+print_cpu(struct cpu_snapshot *c1, struct cpu_snapshot *c2)
+{
+ uint64_t ticks = 0;
+ double etime, percent;
+ kstat_t *old_vm = NULL;
+ kstat_t *old_sys = NULL;
+
+ if (display_pset != -1 && display_pset != c2->cs_pset_id)
+ return;
+
+ /*
+ * the first mpstat output will have c1 = NULL, to give
+ * results since boot
+ */
+ if (c1) {
+ old_vm = &c1->cs_vm;
+ old_sys = &c1->cs_sys;
+
+ /* check there are stats to report */
+ if (!CPU_ACTIVE(c1))
+ return;
+ }
+
+ /* check there are stats to report */
+ if (!CPU_ACTIVE(c2))
+ return;
+
+ ticks = cpu_ticks_delta(old_sys, &c2->cs_sys);
+
+ etime = (double)ticks / hz;
+ if (etime == 0.0) /* Prevent divide by zero errors */
+ etime = 1.0;
+ percent = 100.0 / etime / hz;
+
+ (void) printf("%3d %4.0f %3.0f %4.0f %5.0f %4.0f "
+ "%4.0f %4.0f %4.0f %4.0f %4.0f %5.0f %3.0f %3.0f "
+ "%3.0f %3.0f",
+ c2->cs_id,
+ (kstat_delta(old_vm, &c2->cs_vm, "hat_fault") +
+ kstat_delta(old_vm, &c2->cs_vm, "as_fault")) / etime,
+ kstat_delta(old_vm, &c2->cs_vm, "maj_fault") / etime,
+ kstat_delta(old_sys, &c2->cs_sys, "xcalls") / etime,
+ kstat_delta(old_sys, &c2->cs_sys, "intr") / etime,
+ kstat_delta(old_sys, &c2->cs_sys, "intrthread") / etime,
+ kstat_delta(old_sys, &c2->cs_sys, "pswitch") / etime,
+ kstat_delta(old_sys, &c2->cs_sys, "inv_swtch") / etime,
+ kstat_delta(old_sys, &c2->cs_sys, "cpumigrate") / etime,
+ kstat_delta(old_sys, &c2->cs_sys, "mutex_adenters") / etime,
+ (kstat_delta(old_sys, &c2->cs_sys, "rw_rdfails") +
+ kstat_delta(old_sys, &c2->cs_sys, "rw_wrfails")) / etime,
+ kstat_delta(old_sys, &c2->cs_sys, "syscall") / etime,
+ kstat_delta(old_sys, &c2->cs_sys, "cpu_ticks_user") * percent,
+ kstat_delta(old_sys, &c2->cs_sys, "cpu_ticks_kernel") * percent,
+ kstat_delta(old_sys, &c2->cs_sys, "cpu_ticks_wait") * percent,
+ kstat_delta(old_sys, &c2->cs_sys, "cpu_ticks_idle") * percent);
+
+ if (show_set)
+ (void) printf(" %3d", c2->cs_pset_id);
+ (void) printf("\n");
+}
+
+/*ARGSUSED*/
+static void
+compare_cpu(void *v1, void *v2, void *data)
+{
+ struct cpu_snapshot *c1 = (struct cpu_snapshot *)v1;
+ struct cpu_snapshot *c2 = (struct cpu_snapshot *)v2;
+
+ if (c2 == NULL)
+ return;
+
+ print_cpu(c1, c2);
+}
+
+static int
+pset_has_stats(struct pset_snapshot *p)
+{
+ int count = 0;
+ size_t i;
+ for (i = 0; i < p->ps_nr_cpus; i++) {
+ if (CPU_ACTIVE(p->ps_cpus[i]))
+ count++;
+ }
+ return (count);
+}
+
+static void
+agg_stat(kstat_t *k1, kstat_t *k2, char *name)
+{
+ kstat_named_t *ksn = kstat_data_lookup(k1, name);
+ kstat_named_t *ksn2 = kstat_data_lookup(k2, name);
+ ksn->value.ui64 += ksn2->value.ui64;
+}
+
+static kstat_t *
+agg_vm(struct pset_snapshot *p, kstat_t *ks)
+{
+ size_t i;
+
+ if (p->ps_nr_cpus == NULL)
+ return (NULL);
+
+ if (kstat_copy(&p->ps_cpus[0]->cs_vm, ks))
+ return (NULL);
+
+ for (i = 1; i < p->ps_nr_cpus; i++) {
+ agg_stat(ks, &p->ps_cpus[i]->cs_vm, "hat_fault");
+ agg_stat(ks, &p->ps_cpus[i]->cs_vm, "as_fault");
+ agg_stat(ks, &p->ps_cpus[i]->cs_vm, "maj_fault");
+ }
+
+ return (ks);
+}
+
+static kstat_t *
+agg_sys(struct pset_snapshot *p, kstat_t *ks)
+{
+ size_t i;
+
+ if (p->ps_nr_cpus == NULL)
+ return (NULL);
+
+ if (kstat_copy(&p->ps_cpus[0]->cs_sys, ks))
+ return (NULL);
+
+ for (i = 1; i < p->ps_nr_cpus; i++) {
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "xcalls");
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "intr");
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "intrthread");
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "pswitch");
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "inv_swtch");
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "cpumigrate");
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "mutex_adenters");
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "rw_rdfails");
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "rw_wrfails");
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "syscall");
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "cpu_ticks_user");
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "cpu_ticks_kernel");
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "cpu_ticks_wait");
+ agg_stat(ks, &p->ps_cpus[i]->cs_sys, "cpu_ticks_idle");
+ }
+
+ return (ks);
+}
+
+static uint64_t
+get_nr_ticks(struct pset_snapshot *p1, struct pset_snapshot *p2)
+{
+ kstat_t *old = NULL;
+ kstat_t *new = NULL;
+ size_t i = 0;
+
+ for (i = 0; p1 && i < p1->ps_nr_cpus; i++) {
+ if (p1->ps_cpus[i]->cs_sys.ks_data) {
+ old = &p1->ps_cpus[i]->cs_sys;
+ break;
+ }
+ }
+
+ for (i = 0; p2 && i < p2->ps_nr_cpus; i++) {
+ if (p2->ps_cpus[i]->cs_sys.ks_data) {
+ new = &p2->ps_cpus[i]->cs_sys;
+ break;
+ }
+ }
+
+ if (old == NULL && new == NULL)
+ return (0);
+
+ if (new == NULL) {
+ new = old;
+ old = NULL;
+ }
+
+ return (cpu_ticks_delta(old, new));
+}
+
+static void
+print_pset(struct pset_snapshot *p1, struct pset_snapshot *p2)
+{
+ uint64_t ticks = 0;
+ double etime, percent;
+ kstat_t old_vm;
+ kstat_t old_sys;
+ kstat_t new_vm;
+ kstat_t new_sys;
+
+ if (display_pset != -1 && display_pset != p2->ps_id)
+ return;
+
+ if ((p1 && !pset_has_stats(p1)) || !pset_has_stats(p2))
+ return;
+
+ old_vm.ks_data = old_sys.ks_data = NULL;
+ new_vm.ks_data = new_sys.ks_data = NULL;
+
+ /*
+ * FIXME: these aggs will count "new" or disappeared cpus
+ * in a set, leaving an apparent huge change.
+ */
+
+ /*
+ * the first mpstat output will have p1 = NULL, to give
+ * results since boot
+ */
+ if (p1) {
+ if (!agg_vm(p1, &old_vm) || !agg_sys(p1, &old_sys))
+ goto out;
+ }
+
+ if (!agg_vm(p2, &new_vm) || !agg_sys(p2, &new_sys))
+ goto out;
+
+ ticks = get_nr_ticks(p1, p2);
+
+ etime = (double)ticks / hz;
+ if (etime == 0.0) /* Prevent divide by zero errors */
+ etime = 1.0;
+ percent = 100.0 / p2->ps_nr_cpus / etime / hz;
+
+ (void) printf("%3d %4.0f %3.0f %4.0f %5.0f %4.0f "
+ "%4.0f %4.0f %4.0f %4.0f %4.0f %5.0f %3.0f %3.0f "
+ "%3.0f %3.0f %3d\n",
+ p2->ps_id,
+ (kstat_delta(&old_vm, &new_vm, "hat_fault") +
+ kstat_delta(&old_vm, &new_vm, "as_fault")) / etime,
+ kstat_delta(&old_vm, &new_vm, "maj_fault") / etime,
+ kstat_delta(&old_sys, &new_sys, "xcalls") / etime,
+ kstat_delta(&old_sys, &new_sys, "intr") / etime,
+ kstat_delta(&old_sys, &new_sys, "intrthread") / etime,
+ kstat_delta(&old_sys, &new_sys, "pswitch") / etime,
+ kstat_delta(&old_sys, &new_sys, "inv_swtch") / etime,
+ kstat_delta(&old_sys, &new_sys, "cpumigrate") / etime,
+ kstat_delta(&old_sys, &new_sys, "mutex_adenters") / etime,
+ (kstat_delta(&old_sys, &new_sys, "rw_rdfails") +
+ kstat_delta(&old_sys, &new_sys, "rw_wrfails")) / etime,
+ kstat_delta(&old_sys, &new_sys, "syscall") / etime,
+ kstat_delta(&old_sys, &new_sys, "cpu_ticks_user") * percent,
+ kstat_delta(&old_sys, &new_sys, "cpu_ticks_kernel") * percent,
+ kstat_delta(&old_sys, &new_sys, "cpu_ticks_wait") * percent,
+ kstat_delta(&old_sys, &new_sys, "cpu_ticks_idle") * percent,
+ p2->ps_nr_cpus);
+
+out:
+ free(old_vm.ks_data);
+ free(old_sys.ks_data);
+ free(new_vm.ks_data);
+ free(new_sys.ks_data);
+}
+
+/*ARGSUSED*/
+static void
+compare_pset(void *v1, void *v2, void *data)
+{
+ struct pset_snapshot *p1 = (struct pset_snapshot *)v1;
+ struct pset_snapshot *p2 = (struct pset_snapshot *)v2;
+
+ if (p2 == NULL)
+ return;
+
+ print_pset(p1, p2);
+}
+
+
+/*
+ * Report statistics for a sample interval.
+ */
+static void
+show_cpu_usage(struct snapshot *old, struct snapshot *new, int display_agg)
+{
+ static int lines_until_reprint = 0;
+ enum snapshot_types type = SNAP_CPUS;
+ snapshot_cb cb = compare_cpu;
+
+ if (lines_until_reprint == 0 || nr_active_cpus(new) > 1) {
+ print_header(display_agg, show_set);
+ lines_until_reprint = REPRINT;
+ }
+
+ lines_until_reprint--;
+
+ if (display_agg) {
+ type = SNAP_PSETS;
+ cb = compare_pset;
+ }
+
+ /* print stats since boot the first time round */
+ (void) snapshot_walk(type, old, new, cb, NULL);
+ (void) fflush(stdout);
+}
+
+/*
+ * Usage message on error.
+ */
+static void
+usage(void)
+{
+ (void) fprintf(stderr,
+ "Usage: mpstat [-aq] [-p | -P processor_set] [interval [count]]\n");
+ exit(1);
+}
diff --git a/usr/src/cmd/stat/vmstat/Makefile b/usr/src/cmd/stat/vmstat/Makefile
new file mode 100644
index 0000000000..eafa46f5f6
--- /dev/null
+++ b/usr/src/cmd/stat/vmstat/Makefile
@@ -0,0 +1,62 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (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 2004 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+
+PROG = vmstat
+OBJS = vmstat.o
+SRCS =$(OBJS:%.o=%.c) $(COMMON_SRCS)
+
+include $(SRC)/cmd/Makefile.cmd
+include $(SRC)/cmd/stat/Makefile.stat
+
+LDLIBS += -ldevinfo -lkstat
+CFLAGS += $(CCVERBOSE) -I${STATCOMMONDIR}
+FILEMODE= 0555
+GROUP= bin
+
+lint := LINTFLAGS = -muxs -I$(STATCOMMONDIR)
+
+.KEEP_STATE:
+
+all: $(PROG)
+
+install: all $(ROOTPROG)
+
+$(PROG): $(OBJS) $(COMMON_OBJS)
+ $(LINK.c) -o $(PROG) $(OBJS) $(COMMON_OBJS) $(LDLIBS)
+ $(POST_PROCESS)
+
+%.o : $(STATCOMMONDIR)/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
+
+clean:
+ -$(RM) $(OBJS) $(COMMON_OBJS)
+
+lint: lint_SRCS
+
+include $(SRC)/cmd/Makefile.targ
diff --git a/usr/src/cmd/stat/vmstat/vmstat.c b/usr/src/cmd/stat/vmstat/vmstat.c
new file mode 100644
index 0000000000..2dad80ab7d
--- /dev/null
+++ b/usr/src/cmd/stat/vmstat/vmstat.c
@@ -0,0 +1,527 @@
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * Copyright (c) 1980 Regents of the University of California.
+ * All rights reserved. The Berkeley software License Agreement
+ * specifies the terms and conditions for redistribution.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/* from UCB 5.4 5/17/86 */
+/* from SunOS 4.1, SID 1.31 */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <memory.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <values.h>
+#include <poll.h>
+
+#include "statcommon.h"
+
+char cmdname[] = "vmstat";
+
+static int hz;
+static int pagesize;
+static double etime;
+static int lines = 1;
+static int swflag = 0, cflag = 0, pflag = 0;
+static int suppress_state;
+static long iter = 0;
+static int poll_interval = 0;
+static struct snapshot *ss;
+
+struct iodev_filter df;
+
+#define pgtok(a) ((a) * (pagesize >> 10))
+#define denom(x) ((x) ? (x) : 1)
+#define REPRINT 19
+
+static void dovmstats(struct snapshot *old, struct snapshot *new);
+static void printhdr(int);
+static void dosum(struct sys_snapshot *ss);
+static void dointr(struct snapshot *ss);
+static void docachestats(kstat_ctl_t *kc);
+static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ struct snapshot *old = NULL;
+ enum snapshot_types types = SNAP_SYSTEM;
+ int summary = 0;
+ int intr = 0;
+ kstat_ctl_t *kc;
+
+ pagesize = sysconf(_SC_PAGESIZE);
+ hz = sysconf(_SC_CLK_TCK);
+
+ argc--, argv++;
+ while (argc > 0 && argv[0][0] == '-') {
+ char *cp = *argv++;
+ argc--;
+ while (*++cp) {
+ switch (*cp) {
+
+ case 'S':
+ swflag = !swflag;
+ break;
+ case 's':
+ summary = 1;
+ break;
+ case 'i':
+ intr = 1;
+ break;
+ case 'c':
+ cflag++;
+ break;
+ case 'q':
+ suppress_state = 1;
+ break;
+ case 'p':
+ pflag++; /* detailed paging info */
+ break;
+ default:
+ usage();
+ }
+ }
+ }
+
+ /* consistency with iostat */
+ types |= SNAP_CPUS;
+
+ if (intr)
+ types |= SNAP_INTERRUPTS;
+ if (cflag)
+ types |= SNAP_FLUSHES;
+ if (!intr)
+ types |= SNAP_IODEVS;
+
+ /* max to fit in less than 80 characters */
+ df.if_max_iodevs = 4;
+ df.if_allowed_types = IODEV_DISK;
+ df.if_nr_names = 0;
+ df.if_names = safe_alloc(df.if_max_iodevs * sizeof (char *));
+ (void) memset(df.if_names, 0, df.if_max_iodevs * sizeof (char *));
+
+ while (argc > 0 && !isdigit(argv[0][0]) &&
+ df.if_nr_names < df.if_max_iodevs) {
+ df.if_names[df.if_nr_names] = *argv;
+ df.if_nr_names++;
+ argc--, argv++;
+ }
+
+ kc = open_kstat();
+
+ ss = acquire_snapshot(kc, types, &df);
+
+ /* time, in seconds, since boot */
+ etime = ss->s_sys.ss_ticks / hz;
+
+ if (intr) {
+ dointr(ss);
+ free_snapshot(ss);
+ exit(0);
+ }
+ if (summary) {
+ dosum(&ss->s_sys);
+ free_snapshot(ss);
+ exit(0);
+ }
+
+ if (argc > 0) {
+ long interval;
+ char *endptr;
+
+ errno = 0;
+ interval = strtol(argv[0], &endptr, 10);
+
+ if (errno > 0 || *endptr != '\0' || interval <= 0 ||
+ interval > MAXINT)
+ usage();
+ poll_interval = 1000 * interval;
+ if (poll_interval <= 0)
+ usage();
+ iter = MAXLONG;
+ if (argc > 1) {
+ iter = strtol(argv[1], NULL, 10);
+ if (errno > 0 || *endptr != '\0' || iter <= 0)
+ usage();
+ }
+ if (argc > 2)
+ usage();
+ }
+
+ if (cflag) {
+ free_snapshot(ss);
+ docachestats(kc);
+ exit(0);
+ }
+
+ (void) sigset(SIGCONT, printhdr);
+
+ printhdr(0);
+ dovmstats(old, ss);
+ while (--iter > 0) {
+ (void) poll(NULL, 0, poll_interval);
+ free_snapshot(old);
+ old = ss;
+ ss = acquire_snapshot(kc, types, &df);
+
+ if (!suppress_state)
+ snapshot_report_changes(old, ss);
+
+ /* if config changed, show stats from boot */
+ if (snapshot_has_changed(old, ss)) {
+ free_snapshot(old);
+ old = NULL;
+ }
+
+ dovmstats(old, ss);
+ }
+
+ free_snapshot(old);
+ free_snapshot(ss);
+ free(df.if_names);
+ (void) kstat_close(kc);
+ return (0);
+}
+
+#define DELTA(v) (new->v - (old ? old->v : 0))
+#define ADJ(n) ((adj <= 0) ? n : (adj >= n) ? 1 : n - adj)
+#define adjprintf(fmt, n, val) adj -= (n + 1) - printf(fmt, ADJ(n), val)
+
+static int adj; /* number of excess columns */
+
+/*ARGSUSED*/
+static void
+show_disk(void *v1, void *v2, void *d)
+{
+ struct iodev_snapshot *old = (struct iodev_snapshot *)v1;
+ struct iodev_snapshot *new = (struct iodev_snapshot *)v2;
+ hrtime_t oldtime = new->is_crtime;
+ double hr_etime;
+ double reads, writes;
+
+ if (new == NULL)
+ return;
+
+ if (old)
+ oldtime = old->is_stats.wlastupdate;
+ hr_etime = new->is_stats.wlastupdate - oldtime;
+ if (hr_etime == 0.0)
+ hr_etime = NANOSEC;
+ reads = new->is_stats.reads - (old ? old->is_stats.reads : 0);
+ writes = new->is_stats.writes - (old ? old->is_stats.writes : 0);
+ adjprintf(" %*.0f", 2, (reads + writes) / hr_etime * NANOSEC);
+}
+
+static void
+dovmstats(struct snapshot *old, struct snapshot *new)
+{
+ kstat_t *oldsys = NULL;
+ kstat_t *newsys = &new->s_sys.ss_agg_sys;
+ kstat_t *oldvm = NULL;
+ kstat_t *newvm = &new->s_sys.ss_agg_vm;
+ double percent_factor;
+ ulong_t updates;
+ int count;
+
+ adj = 0;
+
+ if (old) {
+ oldsys = &old->s_sys.ss_agg_sys;
+ oldvm = &old->s_sys.ss_agg_vm;
+ }
+
+ etime = cpu_ticks_delta(oldsys, newsys);
+
+ percent_factor = 100.0 / denom(etime);
+ /*
+ * If any time has passed, convert etime to seconds per CPU
+ */
+ etime = etime >= 1.0 ? (etime / nr_active_cpus(new)) / hz : 1.0;
+ updates = denom(DELTA(s_sys.ss_sysinfo.updates));
+
+ if (--lines == 0)
+ printhdr(0);
+
+ adj = 0;
+
+ if (pflag) {
+ adjprintf(" %*u", 6,
+ pgtok((int)(DELTA(s_sys.ss_vminfo.swap_avail) / updates)));
+ adjprintf(" %*u", 5,
+ pgtok((int)(DELTA(s_sys.ss_vminfo.freemem) / updates)));
+ adjprintf(" %*.0f", 3, kstat_delta(oldvm, newvm, "pgrec")
+ / etime);
+ adjprintf(" %*.0f", 3, (kstat_delta(oldvm, newvm, "hat_fault") +
+ kstat_delta(oldvm, newvm, "as_fault")) / etime);
+ adjprintf(" %*.0f", 3, pgtok(kstat_delta(oldvm, newvm, "dfree"))
+ / etime);
+ adjprintf(" %*ld", 3, pgtok(new->s_sys.ss_deficit));
+ adjprintf(" %*.0f", 3, kstat_delta(oldvm, newvm, "scan")
+ / etime);
+ adjprintf(" %*.0f", 4,
+ pgtok(kstat_delta(oldvm, newvm, "execpgin")) / etime);
+ adjprintf(" %*.0f", 4,
+ pgtok(kstat_delta(oldvm, newvm, "execpgout")) / etime);
+ adjprintf(" %*.0f", 4,
+ pgtok(kstat_delta(oldvm, newvm, "execfree")) / etime);
+ adjprintf(" %*.0f", 4,
+ pgtok(kstat_delta(oldvm, newvm, "anonpgin")) / etime);
+ adjprintf(" %*.0f", 4,
+ pgtok(kstat_delta(oldvm, newvm, "anonpgout")) / etime);
+ adjprintf(" %*.0f", 4,
+ pgtok(kstat_delta(oldvm, newvm, "anonfree")) / etime);
+ adjprintf(" %*.0f", 4,
+ pgtok(kstat_delta(oldvm, newvm, "fspgin")) / etime);
+ adjprintf(" %*.0f", 4,
+ pgtok(kstat_delta(oldvm, newvm, "fspgout")) / etime);
+ adjprintf(" %*.0f\n", 4,
+ pgtok(kstat_delta(oldvm, newvm, "fsfree")) / etime);
+ (void) fflush(stdout);
+ return;
+ }
+
+ adjprintf(" %*lu", 1, DELTA(s_sys.ss_sysinfo.runque) / updates);
+ adjprintf(" %*lu", 1, DELTA(s_sys.ss_sysinfo.waiting) / updates);
+ adjprintf(" %*lu", 1, DELTA(s_sys.ss_sysinfo.swpque) / updates);
+ adjprintf(" %*u", 6, pgtok((int)(DELTA(s_sys.ss_vminfo.swap_avail)
+ / updates)));
+ adjprintf(" %*u", 5, pgtok((int)(DELTA(s_sys.ss_vminfo.freemem)
+ / updates)));
+ adjprintf(" %*.0f", 3, swflag?
+ kstat_delta(oldvm, newvm, "swapin") / etime :
+ kstat_delta(oldvm, newvm, "pgrec") / etime);
+ adjprintf(" %*.0f", 3, swflag?
+ kstat_delta(oldvm, newvm, "swapout") / etime :
+ (kstat_delta(oldvm, newvm, "hat_fault")
+ + kstat_delta(oldvm, newvm, "as_fault"))
+ / etime);
+ adjprintf(" %*.0f", 2, pgtok(kstat_delta(oldvm, newvm, "pgpgin"))
+ / etime);
+ adjprintf(" %*.0f", 2, pgtok(kstat_delta(oldvm, newvm, "pgpgout"))
+ / etime);
+ adjprintf(" %*.0f", 2, pgtok(kstat_delta(oldvm, newvm, "dfree"))
+ / etime);
+ adjprintf(" %*ld", 2, pgtok(new->s_sys.ss_deficit));
+ adjprintf(" %*.0f", 2, kstat_delta(oldvm, newvm, "scan") / etime);
+
+ (void) snapshot_walk(SNAP_IODEVS, old, new, show_disk, NULL);
+
+ count = df.if_max_iodevs - new->s_nr_iodevs;
+ while (count-- > 0)
+ adjprintf(" %*d", 2, 0);
+
+ adjprintf(" %*.0f", 4, kstat_delta(oldsys, newsys, "intr") / etime);
+ adjprintf(" %*.0f", 4, kstat_delta(oldsys, newsys, "syscall") / etime);
+ adjprintf(" %*.0f", 4, kstat_delta(oldsys, newsys, "pswitch") / etime);
+ adjprintf(" %*.0f", 2,
+ kstat_delta(oldsys, newsys, "cpu_ticks_user") * percent_factor);
+ adjprintf(" %*.0f", 2, kstat_delta(oldsys, newsys, "cpu_ticks_kernel")
+ * percent_factor);
+ adjprintf(" %*.0f\n", 2, (kstat_delta(oldsys, newsys, "cpu_ticks_idle")
+ + kstat_delta(oldsys, newsys, "cpu_ticks_wait"))
+ * percent_factor);
+ (void) fflush(stdout);
+}
+
+/*ARGSUSED*/
+static void
+print_disk(void *v, void *v2, void *d)
+{
+ struct iodev_snapshot *iodev = (struct iodev_snapshot *)v2;
+
+ if (iodev == NULL)
+ return;
+
+ (void) printf("%c%c ", iodev->is_name[0], iodev->is_name[2]);
+}
+
+/* ARGSUSED */
+static void
+printhdr(int sig)
+{
+ int i = df.if_max_iodevs - ss->s_nr_iodevs;
+
+ if (pflag) {
+ (void) printf(" memory page ");
+ (void) printf("executable anonymous filesystem \n");
+ (void) printf(" swap free re mf fr de sr ");
+ (void) printf("epi epo epf api apo apf fpi fpo fpf\n");
+ lines = REPRINT;
+ return;
+ }
+
+ (void) printf(" kthr memory page ");
+ (void) printf("disk faults cpu\n");
+
+ if (swflag)
+ (void) printf(" r b w swap free si so pi po fr de sr ");
+ else
+ (void) printf(" r b w swap free re mf pi po fr de sr ");
+
+ (void) snapshot_walk(SNAP_IODEVS, NULL, ss, print_disk, NULL);
+
+ while (i-- > 0)
+ (void) printf("-- ");
+
+ (void) printf(" in sy cs us sy id\n");
+ lines = REPRINT;
+}
+
+static void
+sum_out(char const *pretty, kstat_t *ks, char *name)
+{
+ kstat_named_t *ksn = kstat_data_lookup(ks, name);
+ if (ksn == NULL) {
+ fail(0, "kstat_data_lookup('%s', '%s') failed",
+ ks->ks_name, name);
+ }
+
+ (void) printf("%9llu %s\n", ksn->value.ui64, pretty);
+}
+
+static void
+dosum(struct sys_snapshot *ss)
+{
+ uint64_t total_faults;
+ kstat_named_t *ksn;
+ long double nchtotal;
+ uint64_t nchhits;
+
+ sum_out("swap ins", &ss->ss_agg_vm, "swapin");
+ sum_out("swap outs", &ss->ss_agg_vm, "swapout");
+ sum_out("pages swapped in", &ss->ss_agg_vm, "pgswapin");
+ sum_out("pages swapped out", &ss->ss_agg_vm, "pgswapout");
+
+ ksn = kstat_data_lookup(&ss->ss_agg_vm, "hat_fault");
+ if (ksn == NULL) {
+ fail(0, "kstat_data_lookup('%s', 'hat_fault') failed",
+ ss->ss_agg_vm.ks_name);
+ }
+ total_faults = ksn->value.ui64;
+ ksn = kstat_data_lookup(&ss->ss_agg_vm, "as_fault");
+ if (ksn == NULL) {
+ fail(0, "kstat_data_lookup('%s', 'as_fault') failed",
+ ss->ss_agg_vm.ks_name);
+ }
+ total_faults += ksn->value.ui64;
+
+ (void) printf("%9llu total address trans. faults taken\n",
+ total_faults);
+
+ sum_out("page ins", &ss->ss_agg_vm, "pgin");
+ sum_out("page outs", &ss->ss_agg_vm, "pgout");
+ sum_out("pages paged in", &ss->ss_agg_vm, "pgpgin");
+ sum_out("pages paged out", &ss->ss_agg_vm, "pgpgout");
+ sum_out("total reclaims", &ss->ss_agg_vm, "pgrec");
+ sum_out("reclaims from free list", &ss->ss_agg_vm, "pgfrec");
+ sum_out("micro (hat) faults", &ss->ss_agg_vm, "hat_fault");
+ sum_out("minor (as) faults", &ss->ss_agg_vm, "as_fault");
+ sum_out("major faults", &ss->ss_agg_vm, "maj_fault");
+ sum_out("copy-on-write faults", &ss->ss_agg_vm, "cow_fault");
+ sum_out("zero fill page faults", &ss->ss_agg_vm, "zfod");
+ sum_out("pages examined by the clock daemon", &ss->ss_agg_vm, "scan");
+ sum_out("revolutions of the clock hand", &ss->ss_agg_vm, "rev");
+ sum_out("pages freed by the clock daemon", &ss->ss_agg_vm, "dfree");
+ sum_out("forks", &ss->ss_agg_sys, "sysfork");
+ sum_out("vforks", &ss->ss_agg_sys, "sysvfork");
+ sum_out("execs", &ss->ss_agg_sys, "sysexec");
+ sum_out("cpu context switches", &ss->ss_agg_sys, "pswitch");
+ sum_out("device interrupts", &ss->ss_agg_sys, "intr");
+ sum_out("traps", &ss->ss_agg_sys, "trap");
+ sum_out("system calls", &ss->ss_agg_sys, "syscall");
+
+ nchtotal = (long double) ss->ss_nc.ncs_hits.value.ui64 +
+ (long double) ss->ss_nc.ncs_misses.value.ui64;
+ nchhits = ss->ss_nc.ncs_hits.value.ui64;
+ (void) printf("%9.0Lf total name lookups (cache hits %.0Lf%%)\n",
+ nchtotal, nchhits / denom(nchtotal) * 100);
+
+ sum_out("user cpu", &ss->ss_agg_sys, "cpu_ticks_user");
+ sum_out("system cpu", &ss->ss_agg_sys, "cpu_ticks_kernel");
+ sum_out("idle cpu", &ss->ss_agg_sys, "cpu_ticks_idle");
+ sum_out("wait cpu", &ss->ss_agg_sys, "cpu_ticks_wait");
+}
+
+static void
+dointr(struct snapshot *ss)
+{
+ size_t i;
+ ulong_t total = 0;
+
+ (void) printf("interrupt total rate\n");
+ (void) printf("--------------------------------\n");
+
+ for (i = 0; i < ss->s_nr_intrs; i++) {
+ (void) printf("%-12.8s %10lu %8.0f\n",
+ ss->s_intrs[i].is_name, ss->s_intrs[i].is_total,
+ ss->s_intrs[i].is_total / etime);
+ total += ss->s_intrs[i].is_total;
+ }
+
+ (void) printf("--------------------------------\n");
+ (void) printf("Total %10lu %8.0f\n", total, total / etime);
+}
+
+static void
+docachestats(kstat_ctl_t *kc)
+{
+ struct snapshot *old;
+ struct snapshot *new;
+ int i;
+
+ old = acquire_snapshot(kc, SNAP_FLUSHES, NULL);
+
+ if (iter == 0) {
+ (void) printf("flush statistics: (totals)\n");
+ (void) printf("%8s%8s%8s%8s%8s%8s\n",
+ "usr", "ctx", "rgn", "seg", "pag", "par");
+ (void) printf(" %7d %7d %7d %7d %7d %7d\n",
+ old->s_flushes.f_usr, old->s_flushes.f_ctx,
+ old->s_flushes.f_region, old->s_flushes.f_segment,
+ old->s_flushes.f_page, old->s_flushes.f_partial);
+ return;
+ }
+
+ (void) printf("flush statistics: (interval based)\n");
+ for (i = 0; i < iter; i++) {
+ if (i % REPRINT == 0)
+ (void) printf("%8s%8s%8s%8s%8s%8s\n",
+ "usr", "ctx", "rgn", "seg", "pag", "par");
+
+ (void) poll(NULL, 0, poll_interval);
+ new = acquire_snapshot(kc, SNAP_FLUSHES, NULL);
+
+ (void) printf(" %7d %7d %7d %7d %7d %7d\n",
+ new->s_flushes.f_usr - old->s_flushes.f_usr,
+ new->s_flushes.f_ctx - old->s_flushes.f_ctx,
+ new->s_flushes.f_region - old->s_flushes.f_region,
+ new->s_flushes.f_segment - old->s_flushes.f_segment,
+ new->s_flushes.f_page - old->s_flushes.f_page,
+ new->s_flushes.f_partial- old->s_flushes.f_partial);
+ (void) fflush(stdout);
+ free_snapshot(old);
+ old = new;
+ }
+}
+
+static void
+usage(void)
+{
+ (void) fprintf(stderr,
+ "Usage: vmstat [-cipqsS] [disk ...] [interval [count]]\n");
+ exit(1);
+}