diff options
| author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
|---|---|---|
| committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
| commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
| tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/stat | |
| download | illumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz | |
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/stat')
| -rw-r--r-- | usr/src/cmd/stat/Makefile | 49 | ||||
| -rw-r--r-- | usr/src/cmd/stat/Makefile.stat | 34 | ||||
| -rw-r--r-- | usr/src/cmd/stat/common/acquire.c | 540 | ||||
| -rw-r--r-- | usr/src/cmd/stat/common/acquire_iodevs.c | 727 | ||||
| -rw-r--r-- | usr/src/cmd/stat/common/dsr.c | 968 | ||||
| -rw-r--r-- | usr/src/cmd/stat/common/dsr.h | 142 | ||||
| -rw-r--r-- | usr/src/cmd/stat/common/mnt.c | 161 | ||||
| -rw-r--r-- | usr/src/cmd/stat/common/statcommon.h | 306 | ||||
| -rw-r--r-- | usr/src/cmd/stat/common/walkers.c | 394 | ||||
| -rw-r--r-- | usr/src/cmd/stat/iostat/Makefile | 62 | ||||
| -rw-r--r-- | usr/src/cmd/stat/iostat/iostat.c | 1769 | ||||
| -rw-r--r-- | usr/src/cmd/stat/mpstat/Makefile | 62 | ||||
| -rw-r--r-- | usr/src/cmd/stat/mpstat/mpstat.c | 488 | ||||
| -rw-r--r-- | usr/src/cmd/stat/vmstat/Makefile | 62 | ||||
| -rw-r--r-- | usr/src/cmd/stat/vmstat/vmstat.c | 527 |
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); +} |
