changeset 1488:196daa2cf3db

PSARC/2006/034 fsstat PSARC/2006/116 fsstat update 6335370 RFE: Need generic file system observability (e.g., fsstat)
author rsb
date Sat, 25 Feb 2006 01:33:06 -0800
parents e939870bbc35
children fa842259660e
files usr/src/cmd/stat/Makefile usr/src/cmd/stat/fsstat/Makefile usr/src/cmd/stat/fsstat/fsstat.c usr/src/pkgdefs/SUNWcsu/prototype_com usr/src/uts/common/fs/autofs/auto_vfsops.c usr/src/uts/common/fs/hsfs/hsfs_vfsops.c usr/src/uts/common/fs/lofs/lofs_vfsops.c usr/src/uts/common/fs/mntfs/mntvfsops.c usr/src/uts/common/fs/nfs/nfs4_common.c usr/src/uts/common/fs/nfs/nfs_common.c usr/src/uts/common/fs/pcfs/pc_vfsops.c usr/src/uts/common/fs/proc/prvfsops.c usr/src/uts/common/fs/tmpfs/tmp_vfsops.c usr/src/uts/common/fs/udfs/udf_vfsops.c usr/src/uts/common/fs/ufs/ufs_vfsops.c usr/src/uts/common/fs/vfs.c usr/src/uts/common/fs/vnode.c usr/src/uts/common/fs/zfs/zfs_vfsops.c usr/src/uts/common/os/modconf.c usr/src/uts/common/sys/vfs.h usr/src/uts/common/sys/vnode.h
diffstat 21 files changed, 1961 insertions(+), 129 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/stat/Makefile	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/cmd/stat/Makefile	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
 # 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.
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
 #
 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 # or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
 # CDDL HEADER END
 #
 #
-# Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
 #ident	"%Z%%M%	%I%	%E% SMI"
@@ -30,7 +29,7 @@
 
 include ../Makefile.cmd
 
-SUBDIRS=	iostat mpstat vmstat
+SUBDIRS=	iostat mpstat vmstat fsstat
 
 all :=		TARGET = all
 install :=	TARGET = install
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/stat/fsstat/Makefile	Sat Feb 25 01:33:06 2006 -0800
@@ -0,0 +1,56 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+#ident	"%Z%%M%	%I%	%E% SMI"
+#
+
+PROG = fsstat
+OBJS = fsstat.o
+SRCS =$(OBJS:%.o=%.c)
+
+include $(SRC)/cmd/Makefile.cmd
+
+LDLIBS += -lkstat
+CFLAGS += $(CCVERBOSE) -I${STATCOMMONDIR} -g
+FILEMODE= 0555
+GROUP= bin
+
+lint := LINTFLAGS = -muxs -I$(STATCOMMONDIR)
+
+.KEEP_STATE:
+
+all: $(PROG)
+
+install: all $(ROOTPROG)
+
+$(PROG): $(OBJS)
+	$(LINK.c) -o $(PROG) $(OBJS) $(LDLIBS)
+	$(POST_PROCESS)
+	
+clean:
+	-$(RM) $(OBJS)
+
+lint: lint_SRCS
+
+include $(SRC)/cmd/Makefile.targ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/stat/fsstat/fsstat.c	Sat Feb 25 01:33:06 2006 -0800
@@ -0,0 +1,1079 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident	"%Z%%M%	%I%	%E% SMI"
+
+#include <stdio.h>
+#include <kstat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <sys/vnode.h>
+#include <sys/vfs.h>
+#include <sys/statvfs.h>
+#include <sys/fstyp.h>
+#include <sys/fsid.h>
+#include <sys/mnttab.h>
+#include <values.h>
+#include <poll.h>
+#include <ctype.h>
+#include <libintl.h>
+
+#define	OPTIONS	"PT:afginv"
+
+/* Time stamp values */
+#define	NODATE	0	/* Default:  No time stamp */
+#define	DDATE	1	/* Standard date format */
+#define	UDATE	2	/* Internal representation of Unix time */
+
+#define	RETRY_DELAY	250	/* Timeout for poll() */
+#define	HEADERLINES	22	/* Number of lines between display headers */
+
+#define	LBUFSZ		64	/* Generic size for local buffer */
+
+/*
+ * The following are used for the nicenum() function
+ */
+#define	KILO_VAL	1024
+#define	ONE_INDEX	3
+
+#define	NENTITY_INIT	1	/* Initial number of entities to allocate */
+
+/*
+ * We need to have a mechanism for an old/previous and new/current vopstat
+ * structure.  We only need two per entity and we can swap between them.
+ */
+#define	VS_SIZE	2	/* Size of vopstat array */
+#define	CUR_INDEX	(vs_i)
+#define	PREV_INDEX	((vs_i == 0) ? 1 : 0)	/* Opposite of CUR_INDEX */
+#define	BUMP_INDEX()	vs_i = ((vs_i == 0) ? 1 : 0)
+
+/*
+ * An "entity" is anything we're collecting statistics on, it could
+ * be a mountpoint or an FS-type.
+ * e_name is the name of the entity (e.g. mount point or FS-type)
+ * e_ksname is the name of the associated kstat
+ * e_vs is an array of vopstats.  This is used to keep track of "previous"
+ * and "current" vopstats.
+ */
+typedef struct entity {
+	char		*e_name;		/* name of entity */
+	vopstats_t	*e_vs;			/* Array of vopstats */
+	ulong_t		e_fsid;			/* fsid for ENTYPE_MNTPT only */
+	int		e_type;			/* type of entity */
+	char		e_ksname[KSTAT_STRLEN];	/* kstat name */
+} entity_t;
+
+/* Types of entities (e_type) */
+#define	ENTYPE_UNKNOWN	0	/* UNKNOWN must be zero since we calloc() */
+#define	ENTYPE_FSTYPE	1
+#define	ENTYPE_MNTPT	2
+
+/* If more sub-one units are added, make sure to adjust ONE_INDEX above */
+static char units[] = "num KMGTPE";
+
+static char	*cmdname;	/* name of this command */
+
+static int	vs_i = 0;	/* Index of current vs[] slot */
+
+static void
+usage()
+{
+	(void) fprintf(stderr,
+	    gettext("Usage: %s [-a|f|i|n|v] [-P] [ fstype | fspath ]... "
+	    "[interval [count]]\n"), cmdname);
+	exit(2);
+}
+
+/*
+ * Given a 64-bit number and a starting unit (e.g., n - nanoseconds),
+ * convert the number to a 5-character representation including any
+ * decimal point and single-character unit.  Put that representation
+ * into the array "buf" (which had better be big enough).
+ */
+char *
+nicenum(uint64_t num, char unit, char *buf)
+{
+	uint64_t n = num;
+	int unit_index;
+	int index;
+	char u;
+
+	/* If the user passed in a NUL/zero unit, use the blank value for 1 */
+	if (unit == '\0')
+		unit = ' ';
+
+	unit_index = 0;
+	while (units[unit_index] != unit) {
+		unit_index++;
+		if (unit_index > sizeof (units) - 1) {
+			(void) sprintf(buf, "??");
+			return (buf);
+		}
+	}
+
+	index = 0;
+	while (n >= KILO_VAL) {
+		n = (n + (KILO_VAL / 2)) / KILO_VAL; /* Round up or down */
+		index++;
+		unit_index++;
+	}
+
+	if (unit_index >= sizeof (units) - 1) {
+		(void) sprintf(buf, "??");
+		return (buf);
+	}
+
+	u = units[unit_index];
+
+	if (unit_index == ONE_INDEX) {
+		(void) sprintf(buf, "%llu", (u_longlong_t)n);
+	} else if (n < 10 && (num & (num - 1)) != 0) {
+		(void) sprintf(buf, "%.2f%c",
+		    (double)num / (1ULL << 10 * index), u);
+	} else if (n < 100 && (num & (num - 1)) != 0) {
+		(void) sprintf(buf, "%.1f%c",
+		    (double)num / (1ULL << 10 * index), u);
+	} else {
+		(void) sprintf(buf, "%llu%c", (u_longlong_t)n, u);
+	}
+
+	return (buf);
+}
+
+
+#define	RAWVAL(ptr, member) ((ptr)->member.value.ui64)
+#define	DELTA(member)	\
+	(newvsp->member.value.ui64 - (oldvsp ? oldvsp->member.value.ui64 : 0))
+#define	OLDPRINTSTAT(isnice, nicestring, niceval, rawstring, rawval)	\
+		(isnice) ? 						\
+			(void) printf((nicestring), (niceval))		\
+		:							\
+			(void) printf((rawstring), (rawval))
+#define	PRINTSTAT(isnice, nicestring, rawstring, rawval, unit, buf)	\
+	(isnice) ?	 						\
+		(void) printf((nicestring), nicenum(rawval, unit, buf))	\
+	:								\
+		(void) printf((rawstring), (rawval))
+
+/* Values for display flag */
+#define	DISP_HEADER	0x1
+#define	DISP_RAW	0x2
+
+/*
+ * The policy for dealing with multiple flags is dealt with here.
+ * Currently, if we are displaying raw output, then don't allow
+ * headers to be printed.
+ */
+int
+dispflag_policy(int printhdr, int dispflag)
+{
+	/* If we're not displaying raw output, then allow headers to print */
+	if ((dispflag & DISP_RAW) == 0) {
+		if (printhdr) {
+			dispflag |= DISP_HEADER;
+		}
+	}
+
+	return (dispflag);
+}
+
+static void
+dflt_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
+{
+	int		niceflag = ((dispflag & DISP_RAW) == 0);
+	longlong_t	nnewfile;
+	longlong_t	nnamerm;
+	longlong_t	nnamechg;
+	longlong_t	nattrret;
+	longlong_t	nattrchg;
+	longlong_t	nlookup;
+	longlong_t	nreaddir;
+	longlong_t	ndataread;
+	longlong_t	ndatawrite;
+	longlong_t	readthruput;
+	longlong_t	writethruput;
+	char		buf[LBUFSZ];
+
+	nnewfile = DELTA(ncreate) + DELTA(nmkdir) + DELTA(nsymlink);
+	nnamerm = DELTA(nremove) + DELTA(nrmdir);
+	nnamechg = DELTA(nrename) + DELTA(nlink) + DELTA(nsymlink);
+	nattrret = DELTA(ngetattr) + DELTA(naccess) +
+				DELTA(ngetsecattr) + DELTA(nfid);
+	nattrchg = DELTA(nsetattr) + DELTA(nsetsecattr) + DELTA(nspace);
+	nlookup = DELTA(nlookup);
+	nreaddir = DELTA(nreaddir);
+	ndataread = DELTA(nread);
+	ndatawrite = DELTA(nwrite);
+	readthruput = DELTA(read_bytes);
+	writethruput = DELTA(write_bytes);
+
+	if (dispflag & DISP_HEADER) {
+		(void) printf(gettext(
+" new  name   name  attr  attr lookup rddir  read read  write write\n"
+" file remov  chng   get   set    ops   ops   ops bytes   ops bytes\n"));
+	}
+
+	PRINTSTAT(niceflag, "%5s ", "%lld:", nnewfile, ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", nnamerm, ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", nnamechg, ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", nattrret, ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", nattrchg, ' ', buf);
+	PRINTSTAT(niceflag, " %5s ", "%lld:", nlookup, ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", nreaddir, ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", ndataread, ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", readthruput, ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", ndatawrite, ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", writethruput, ' ', buf);
+	(void) printf("%s\n", name);
+}
+
+static void
+io_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
+{
+	int		niceflag = ((dispflag & DISP_RAW) == 0);
+	char		buf[LBUFSZ];
+
+	if (dispflag & DISP_HEADER) {
+		(void) printf(gettext(
+" read read  write write rddir rddir rwlock rwulock\n"
+"  ops bytes   ops bytes   ops bytes    ops     ops\n"));
+	}
+
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nread), ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(read_bytes), ' ', buf);
+
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nwrite), ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(write_bytes), ' ', buf);
+
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(readdir_bytes), ' ', buf);
+
+	PRINTSTAT(niceflag, " %5s   ", "%lld:", DELTA(nrwlock), ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrwunlock), ' ', buf);
+
+	(void) printf("%s\n", name);
+}
+
+static void
+vm_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
+{
+	int		niceflag = ((dispflag & DISP_RAW) == 0);
+	char		buf[LBUFSZ];
+
+	if (dispflag & DISP_HEADER) {
+		(void) printf(
+		    gettext("  map addmap delmap getpag putpag pagio\n"));
+	}
+
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmap), ' ', buf);
+	PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(naddmap), ' ', buf);
+	PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ndelmap), ' ', buf);
+	PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetpage), ' ', buf);
+	PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nputpage), ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(npageio), ' ', buf);
+	(void) printf("%s\n", name);
+}
+
+static void
+attr_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
+{
+	int		niceflag = ((dispflag & DISP_RAW) == 0);
+	char		buf[LBUFSZ];
+
+	if (dispflag & DISP_HEADER) {
+		(void) printf(gettext("getattr setattr getsec  setsec\n"));
+	}
+
+	PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetattr), ' ', buf);
+	PRINTSTAT(niceflag, "  %5s ", "%lld:", DELTA(nsetattr), ' ', buf);
+	PRINTSTAT(niceflag, "  %5s ", "%lld:", DELTA(ngetsecattr), ' ', buf);
+	PRINTSTAT(niceflag, "  %5s ", "%lld:", DELTA(nsetsecattr), ' ', buf);
+
+	(void) printf("%s\n", name);
+}
+
+static void
+naming_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
+{
+	int		niceflag = ((dispflag & DISP_RAW) == 0);
+	char		buf[LBUFSZ];
+
+	if (dispflag & DISP_HEADER) {
+		(void) printf(gettext(
+	"lookup creat remov  link renam mkdir rmdir rddir symlnk rdlnk\n"));
+	}
+
+	PRINTSTAT(niceflag, "%5s  ", "%lld:", DELTA(nlookup), ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(ncreate), ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nremove), ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nlink), ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrename), ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmkdir), ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrmdir), ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), ' ', buf);
+	PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsymlink), ' ', buf);
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreadlink), ' ', buf);
+	(void) printf("%s\n", name);
+}
+
+
+#define	PRINT_VOPSTAT_CMN(niceflag, vop)				\
+	if (niceflag)							\
+		(void) printf("%10s ", #vop);				\
+	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(n##vop), ' ', buf);
+
+#define	PRINT_VOPSTAT(niceflag, vop) 					\
+	PRINT_VOPSTAT_CMN(niceflag, vop);				\
+	if (niceflag)							\
+		(void) printf("\n");
+
+#define	PRINT_VOPSTAT_IO(niceflag, vop)					\
+	PRINT_VOPSTAT_CMN(niceflag, vop);				\
+	PRINTSTAT(niceflag, " %5s\n", "%lld:",				\
+		DELTA(vop##_bytes), ' ', buf);
+
+static void
+vop_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
+{
+	int		niceflag = ((dispflag & DISP_RAW) == 0);
+	char		buf[LBUFSZ];
+
+	if (niceflag) {
+		(void) printf("%s\n", name);
+		(void) printf(gettext(" operation  #ops  bytes\n"));
+	}
+
+	PRINT_VOPSTAT(niceflag, open);
+	PRINT_VOPSTAT(niceflag, close);
+	PRINT_VOPSTAT_IO(niceflag, read);
+	PRINT_VOPSTAT_IO(niceflag, write);
+	PRINT_VOPSTAT(niceflag, ioctl);
+	PRINT_VOPSTAT(niceflag, setfl);
+	PRINT_VOPSTAT(niceflag, getattr);
+	PRINT_VOPSTAT(niceflag, setattr);
+	PRINT_VOPSTAT(niceflag, access);
+	PRINT_VOPSTAT(niceflag, lookup);
+	PRINT_VOPSTAT(niceflag, create);
+	PRINT_VOPSTAT(niceflag, remove);
+	PRINT_VOPSTAT(niceflag, link);
+	PRINT_VOPSTAT(niceflag, rename);
+	PRINT_VOPSTAT(niceflag, mkdir);
+	PRINT_VOPSTAT(niceflag, rmdir);
+	PRINT_VOPSTAT_IO(niceflag, readdir);
+	PRINT_VOPSTAT(niceflag, symlink);
+	PRINT_VOPSTAT(niceflag, readlink);
+	PRINT_VOPSTAT(niceflag, fsync);
+	PRINT_VOPSTAT(niceflag, inactive);
+	PRINT_VOPSTAT(niceflag, fid);
+	PRINT_VOPSTAT(niceflag, rwlock);
+	PRINT_VOPSTAT(niceflag, rwunlock);
+	PRINT_VOPSTAT(niceflag, seek);
+	PRINT_VOPSTAT(niceflag, cmp);
+	PRINT_VOPSTAT(niceflag, frlock);
+	PRINT_VOPSTAT(niceflag, space);
+	PRINT_VOPSTAT(niceflag, realvp);
+	PRINT_VOPSTAT(niceflag, getpage);
+	PRINT_VOPSTAT(niceflag, putpage);
+	PRINT_VOPSTAT(niceflag, map);
+	PRINT_VOPSTAT(niceflag, addmap);
+	PRINT_VOPSTAT(niceflag, delmap);
+	PRINT_VOPSTAT(niceflag, poll);
+	PRINT_VOPSTAT(niceflag, dump);
+	PRINT_VOPSTAT(niceflag, pathconf);
+	PRINT_VOPSTAT(niceflag, pageio);
+	PRINT_VOPSTAT(niceflag, dumpctl);
+	PRINT_VOPSTAT(niceflag, dispose);
+	PRINT_VOPSTAT(niceflag, getsecattr);
+	PRINT_VOPSTAT(niceflag, setsecattr);
+	PRINT_VOPSTAT(niceflag, shrlock);
+	PRINT_VOPSTAT(niceflag, vnevent);
+
+	if (niceflag) {
+		/* Make it easier on the eyes */
+		(void) printf("\n");
+	} else {
+		(void) printf("%s\n", name);
+	}
+}
+
+
+/*
+ * Retrieve the vopstats.  If kspp (pointer to kstat_t pointer) is non-NULL,
+ * then pass it back to the caller.
+ *
+ * Returns 0 on success, non-zero on failure.
+ */
+int
+get_vopstats(kstat_ctl_t *kc, char *ksname, vopstats_t *vsp, kstat_t **kspp)
+{
+	kstat_t		*ksp;
+
+	if (ksname == NULL || *ksname == 0)
+		return (1);
+
+	errno = 0;
+	/* wait for a possibly up-to-date chain */
+	while (kstat_chain_update(kc) == -1) {
+		if (errno == EAGAIN) {
+			errno = 0;
+			(void) poll(NULL, 0, RETRY_DELAY);
+			continue;
+		}
+		perror(gettext("kstat_chain_update"));
+		exit(1);
+	}
+
+	if ((ksp = kstat_lookup(kc, NULL, -1, ksname)) == NULL) {
+		return (1);
+	}
+
+	if (kstat_read(kc, ksp, vsp) == -1) {
+		return (1);
+	}
+
+	if (kspp)
+		*kspp = ksp;
+
+	return (0);
+}
+
+/*
+ * Given a file system type name, determine if it's part of the
+ * exception list of file systems that are not to be displayed.
+ */
+int
+is_exception(char *fsname)
+{
+	char **xlp;	/* Pointer into the exception list */
+
+	static char *exception_list[] = {
+		"specfs",
+		"fifofs",
+		"fd",
+		"swapfs",
+		"ctfs",
+		"objfs",
+		"nfsdyn",
+		NULL
+	};
+
+	for (xlp = &exception_list[0]; *xlp != NULL; xlp++) {
+		if (strcmp(fsname, *xlp) == 0)
+			return (1);
+	}
+
+	return (0);
+}
+
+/*
+ * Plain and simple, build an array of names for fstypes
+ * Returns 0, if it encounters a problem.
+ */
+int
+build_fstype_list(char ***fstypep)
+{
+	int	i;
+	int	nfstype;
+	char	buf[FSTYPSZ + 1];
+
+	if ((nfstype = sysfs(GETNFSTYP)) < 0) {
+		perror(gettext("sysfs(GETNFSTYP)"));
+		return (0);
+	}
+
+	if ((*fstypep = calloc(nfstype, sizeof (char *))) == NULL) {
+		perror(gettext("calloc on fstypes"));
+		return (0);
+	}
+
+	for (i = 1; i < nfstype; i++) {
+		if (sysfs(GETFSTYP, i, buf) < 0) {
+			perror(gettext("sysfs(GETFSTYP)"));
+			return (0);
+		}
+
+		if (buf[0] == 0)
+			continue;
+
+		/* If this is part of the exception list, move on */
+		if (is_exception(buf))
+			continue;
+
+		if (((*fstypep)[i] = strdup(buf)) == NULL) {
+			perror(gettext("strdup() of fstype name"));
+			return (0);
+		}
+	}
+
+	return (i);
+}
+
+/*
+ * After we're done with getopts(), process the rest of the
+ * operands.  We have three cases and this is the priority:
+ *
+ * 1) [ operand... ] interval count
+ * 2) [ operand... ] interval
+ * 3) [ operand... ]
+ *
+ * The trick is that any of the operands might start with a number or even
+ * be made up exclusively of numbers (and we have to handle negative numbers
+ * in case a user/script gets out of line).  If we find two operands at the
+ * end of the list then we claim case 1.  If we find only one operand at the
+ * end made up only of number, then we claim case 2.  Otherwise, case 3.
+ * BTW, argc, argv don't change.
+ */
+int
+parse_operands(
+	int		argc,
+	char		**argv,
+	int		optind,
+	long		*interval,
+	long		*count,
+	entity_t	**entityp)	/* Array of stat-able entities */
+{
+	int	nentities = 0;	/* Number of entities found */
+	int	out_of_range;	/* Set if 2nd-to-last operand out-of-range */
+
+	if (argc == optind)
+		return (nentities);	/* None found, returns 0 */
+	/*
+	 * We know exactly what the maximum number of entities is going
+	 * to be:  argc - optind
+	 */
+	if ((*entityp = calloc((argc - optind), sizeof (entity_t))) == NULL) {
+		perror(gettext("calloc"));
+		return (-1);
+	}
+
+	for (/* void */; argc > optind; optind++) {
+		char	*endptr;
+
+		/* If we have more than two operands left to process */
+		if ((argc - optind) > 2) {
+			(*entityp)[nentities++].e_name = strdup(argv[optind]);
+			continue;
+		}
+
+		/* If we're here, then we only have one or two operands left */
+		errno = 0;
+		out_of_range = 0;
+		*interval = strtol(argv[optind], &endptr, 10);
+		if (*endptr && !isdigit((int)*endptr)) {
+			/* Operand was not a number */
+			(*entityp)[nentities++].e_name = strdup(argv[optind]);
+			continue;
+		} else if (errno == ERANGE || *interval <= 0 ||
+							*interval > MAXLONG) {
+			/* Operand was a number, just out of range */
+			out_of_range++;
+		}
+
+		/*
+		 * The last operand we saw was a number.  If it happened to
+		 * be the last operand, then it is the interval...
+		 */
+		if ((argc - optind) == 1) {
+			/* ...but we need to check the range. */
+			if (out_of_range) {
+				(void) fprintf(stderr, gettext(
+				    "interval must be between 1 and "
+				    "%ld (inclusive)\n"), MAXLONG);
+				return (-1);
+			} else {
+				/*
+				 * The value of the interval is valid. Set
+				 * count to something really big so it goes
+				 * virtually forever.
+				 */
+				*count = MAXLONG;
+				break;
+			}
+		}
+
+		/*
+		 * At this point, we *might* have the interval, but if the
+		 * next operand isn't a number, then we don't have either
+		 * the interval nor the count.  Both must be set to the
+		 * defaults.  In that case, both the current and the previous
+		 * operands are stat-able entities.
+		 */
+		errno = 0;
+		*count = strtol(argv[optind + 1], &endptr, 10);
+		if (*endptr && !isdigit((int)*endptr)) {
+			/*
+			 * Faked out!  The last operand wasn't a number so
+			 * the current and previous operands should be
+			 * stat-able entities. We also need to reset interval.
+			 */
+			*interval = 0;
+			(*entityp)[nentities++].e_name = strdup(argv[optind++]);
+			(*entityp)[nentities++].e_name = strdup(argv[optind++]);
+		} else if (out_of_range || errno == ERANGE || *count <= 0) {
+			(void) fprintf(stderr, gettext(
+			    "Both interval and count must be between 1 "
+			    "and %ld (inclusive)\n"), MAXLONG);
+			return (-1);
+		}
+		break;	/* Done! */
+	}
+	return (nentities);
+}
+
+/*
+ * set_mntpt() looks at the entity's name (e_name) and finds its
+ * mountpoint.  To do this, we need to build a list of mountpoints
+ * from /etc/mnttab.  We only need to do this once and we don't do it
+ * if we don't need to look at any mountpoints.
+ * Returns 0 on success, non-zero if it couldn't find a mount-point.
+ */
+int
+set_mntpt(entity_t *ep)
+{
+	static struct mnt {
+		struct mnt	*m_next;
+		char		*m_mntpt;
+		ulong_t		m_fsid;	/* From statvfs(), set only as needed */
+	} *mnt_list = NULL;	/* Linked list of mount-points */
+	struct mnt *mntp;
+	struct statvfs statvfsbuf;
+	char *original_name = ep->e_name;
+	char path[PATH_MAX];
+
+	if (original_name == NULL)		/* Shouldn't happen */
+		return (1);
+
+	/* We only set up mnt_list the first time this is called */
+	if (mnt_list == NULL) {
+		FILE *fp;
+		struct mnttab mnttab;
+
+		if ((fp = fopen(MNTTAB, "r")) == NULL) {
+			perror(MNTTAB);
+			return (1);
+		}
+		resetmnttab(fp);
+		/*
+		 * We insert at the front of the list so that when we
+		 * search entries we'll have the last mounted entries
+		 * first in the list so that we can match the longest
+		 * mountpoint.
+		 */
+		while (getmntent(fp, &mnttab) == 0) {
+			if ((mntp = malloc(sizeof (*mntp))) == NULL) {
+				perror(gettext("Can't create mount list"));
+				return (1);
+			}
+			mntp->m_mntpt = strdup(mnttab.mnt_mountp);
+			mntp->m_next = mnt_list;
+			mnt_list = mntp;
+		}
+		(void) fclose(fp);
+	}
+
+	if (realpath(original_name, path) == NULL) {
+		perror(original_name);
+		return (1);
+	}
+
+	/*
+	 * Now that we have the path, walk through the mnt_list and
+	 * look for the first (best) match.
+	 */
+	for (mntp = mnt_list; mntp; mntp = mntp->m_next) {
+		if (strncmp(path, mntp->m_mntpt, strlen(mntp->m_mntpt)) == 0) {
+			if (mntp->m_fsid == 0) {
+				if (statvfs(mntp->m_mntpt, &statvfsbuf)) {
+					/* Can't statvfs so no match */
+					continue;
+				} else {
+					mntp->m_fsid = statvfsbuf.f_fsid;
+				}
+			}
+
+			if (ep->e_fsid != mntp->m_fsid) {
+				/* No match - Move on */
+				continue;
+			}
+
+			break;
+		}
+	}
+
+	if (mntp == NULL) {
+		(void) fprintf(stderr, gettext(
+		    "Can't find mount point for %s\n"), path);
+		return (1);
+	}
+
+	ep->e_name = strdup(mntp->m_mntpt);
+	free(original_name);
+	return (0);
+}
+
+/*
+ * We have an array of entities that are potentially stat-able.  Using
+ * the name (e_name) of the entity, attempt to construct a ksname suitable
+ * for use by kstat_lookup(3kstat) and fill it into the e_ksname member.
+ *
+ * We check the e_name against the list of file system types.  If there is
+ * no match then test to see if the path is valid.  If the path is valid,
+ * then determine the mountpoint.
+ */
+void
+set_ksnames(entity_t *entities, int nentities, char **fstypes, int nfstypes)
+{
+	int		i, j;
+	struct statvfs	statvfsbuf;
+
+	for (i = 0; i < nentities; i++) {
+		entity_t	*ep = &entities[i];
+
+		/* Check the name against the list of fstypes */
+		for (j = 1; j < nfstypes; j++) {
+			if (fstypes[j] && ep->e_name &&
+			    strcmp(ep->e_name, fstypes[j]) == 0) {
+				/* It's a file system type */
+				ep->e_type = ENTYPE_FSTYPE;
+				(void) snprintf(ep->e_ksname,
+						KSTAT_STRLEN, "%s%s",
+						VOPSTATS_STR, ep->e_name);
+				/* Now allocate the vopstats array */
+				ep->e_vs = calloc(VS_SIZE, sizeof (vopstats_t));
+				if (entities[i].e_vs == NULL) {
+					perror(gettext("calloc() vopstats"));
+					exit(1);
+				}
+				break;
+			}
+		}
+		if (j < nfstypes)	/* Found it! */
+			continue;
+
+		/*
+		 * If the entity in the exception list of fstypes, then
+		 * null out the entry so it isn't displayed and move along.
+		 */
+		if (is_exception(ep->e_name)) {
+			ep->e_ksname[0] = 0;
+			continue;
+		}
+
+		/* If we didn't find it, see if it's a path */
+		if (ep->e_name == NULL || statvfs(ep->e_name, &statvfsbuf)) {
+			/* Error - Make sure the entry is nulled out */
+			ep->e_ksname[0] = 0;
+			continue;
+		}
+		(void) snprintf(ep->e_ksname, KSTAT_STRLEN, "%s%lx",
+		    VOPSTATS_STR, statvfsbuf.f_fsid);
+		ep->e_fsid = statvfsbuf.f_fsid;
+		if (set_mntpt(ep)) {
+			(void) fprintf(stderr,
+			    gettext("Can't determine type of \"%s\"\n"),
+			    ep->e_name ? ep->e_name : gettext("<NULL>"));
+		} else {
+			ep->e_type = ENTYPE_MNTPT;
+		}
+
+		/* Now allocate the vopstats array */
+		ep->e_vs = calloc(VS_SIZE, sizeof (vopstats_t));
+		if (entities[i].e_vs == NULL) {
+			perror(gettext("Can't calloc vopstats"));
+			exit(1);
+		}
+	}
+}
+
+void
+print_time(int type)
+{
+	time_t	t;
+
+	if (time(&t) != -1) {
+		if (type == UDATE) {
+			(void) printf("%ld\n", t);
+		} else if (type == DDATE) {
+			char	*dstr;
+
+			dstr = ctime(&t);
+			if (dstr) {
+				(void) printf("%s", dstr);
+			}
+		}
+	}
+}
+
+/*
+ * The idea is that 'dspfunc' should only be modified from the default
+ * once since the display options are mutually exclusive.  If 'dspfunc'
+ * only contains the default display function, then all is good and we
+ * can set it to the new display function.  Otherwise, bail.
+ */
+void
+set_dispfunc(
+	void (**dspfunc)(char *, vopstats_t *, vopstats_t *, int),
+	void (*newfunc)(char *, vopstats_t *, vopstats_t *, int))
+{
+	if (*dspfunc != dflt_display) {
+		(void) fprintf(stderr, gettext(
+		"%s: Display options -{a|f|i|n|v} are mutually exclusive\n"),
+		    cmdname);
+		usage();
+	}
+	*dspfunc = newfunc;
+}
+
+int
+main(int argc, char *argv[])
+{
+	int		c;
+	int		i, j;		/* Generic counters */
+	int		nentities_found;
+	int		linesout;	/* Keeps track of lines printed */
+	int		printhdr = 0;	/* Print a header?  0 = no, 1 = yes */
+	int		nfstypes;	/* Number of fstypes */
+	int		dispflag = 0;	/* Flags for display control */
+	int		timestamp = NODATE;	/* Default: no time stamp */
+	long		count = 0;	/* Number of iterations for display */
+	long		interval = 0;
+	boolean_t	fstypes_only = B_FALSE;	/* Display fstypes only */
+	char		**fstypes;	/* Array of names of all fstypes */
+	int		nentities;	/* Number of stat-able entities */
+	entity_t	*entities;	/* Array of stat-able entities */
+	kstat_ctl_t	*kc;
+	void (*dfunc)(char *, vopstats_t *, vopstats_t *, int) = dflt_display;
+
+	extern int	optind;
+
+	cmdname = argv[0];
+	while ((c = getopt(argc, argv, OPTIONS)) != EOF) {
+		switch (c) {
+
+		default:
+			usage();
+			break;
+
+		case 'P':	/* Parsable output */
+			dispflag |= DISP_RAW;
+			break;
+
+		case 'T':	/* Timestamp */
+			if (optarg) {
+				if (strcmp(optarg, "u") == 0) {
+					timestamp = UDATE;
+				} else if (strcmp(optarg, "d") == 0) {
+					timestamp = DDATE;
+				}
+			}
+
+			/* If it was never set properly... */
+			if (timestamp == NODATE) {
+				(void) fprintf(stderr, gettext(
+				"%s: -T option requires either 'u' or 'd'\n"),
+					cmdname);
+				usage();
+			}
+			break;
+
+		case 'a':
+			set_dispfunc(&dfunc, attr_display);
+			break;
+
+		case 'f':
+			set_dispfunc(&dfunc, vop_display);
+			break;
+
+		case 'i':
+			set_dispfunc(&dfunc, io_display);
+			break;
+
+		case 'n':
+			set_dispfunc(&dfunc, naming_display);
+			break;
+
+		case 'v':
+			set_dispfunc(&dfunc, vm_display);
+			break;
+		}
+	}
+
+	if ((dispflag & DISP_RAW) && (timestamp != NODATE)) {
+		(void) fprintf(stderr, gettext(
+			"-P and -T options are mutually exclusive\n"));
+		usage();
+	}
+
+	/* Gather the list of filesystem types */
+	if ((nfstypes = build_fstype_list(&fstypes)) == 0) {
+		(void) fprintf(stderr,
+		    gettext("Can't build list of fstypes\n"));
+		exit(1);
+	}
+
+	nentities = parse_operands(
+	    argc, argv, optind, &interval, &count, &entities);
+
+	if (nentities == -1)	/* Set of operands didn't parse properly  */
+		usage();
+
+	/*
+	 * If we had no operands (except for interval/count) then we
+	 * fill in the entities[] array with all the fstypes.
+	 */
+	if (nentities == 0) {
+		fstypes_only = B_TRUE;
+
+		if ((entities = calloc(nfstypes, sizeof (entity_t))) == NULL) {
+			(void) fprintf(stderr,
+			    gettext("Can't calloc fstype stats\n"));
+			exit(1);
+		}
+
+		for (i = 1; i < nfstypes; i++) {
+			if (fstypes[i]) {
+				entities[nentities].e_name = strdup(fstypes[i]);
+				nentities++;
+			}
+		}
+	}
+
+	set_ksnames(entities, nentities, fstypes, nfstypes);
+
+	if ((kc = kstat_open()) == NULL) {
+		perror(gettext("kstat_open"));
+		exit(1);
+	}
+
+	/*
+	 * The following loop walks through the entities[] list to "prime
+	 * the pump"
+	 */
+	for (j = 0, linesout = 0; j < nentities; j++) {
+		entity_t *ent = &entities[j];
+		vopstats_t *vsp = &ent->e_vs[CUR_INDEX];
+		kstat_t *ksp = NULL;
+
+		if (get_vopstats(kc, ent->e_ksname, vsp, &ksp) == 0) {
+			(*dfunc)(ent->e_name, NULL, vsp,
+			    dispflag_policy(linesout == 0, dispflag));
+			linesout++;
+		} else {
+			/*
+			 * If we can't find it the first time through, then
+			 * get rid of it.
+			 */
+			entities[j].e_ksname[0] = 0;
+
+			/*
+			 * If we're only displaying the fstypes (default
+			 * with no other entities requested) then don't
+			 * complain about any file systems that might not
+			 * be loaded.  Otherwise, let the user know that
+			 * he chose poorly.
+			 */
+			if (fstypes_only == B_FALSE) {
+				(void) fprintf(stderr, gettext(
+				    "No statistics available for %s\n"),
+				    entities[j].e_name);
+			}
+		}
+	}
+
+	BUMP_INDEX();	/* Swap the previous/current indices */
+	for (i = 1; i <= count; i++) {
+		/*
+		 * No telling how many lines will be printed in any interval.
+		 * There should be a minimum of HEADERLINES between any
+		 * header.  If we exceed that, no big deal.
+		 */
+		if (linesout > HEADERLINES) {
+			linesout = 0;
+			printhdr = 1;
+		}
+		(void) poll(NULL, 0, interval*1000);
+
+		if (timestamp) {
+			print_time(timestamp);
+			linesout++;
+		}
+
+		for (j = 0, nentities_found = 0; j < nentities; j++) {
+			entity_t *ent = &entities[j];
+
+			/*
+			 * If this entry has been cleared, don't attempt
+			 * to process it.
+			 */
+			if (ent->e_ksname[0] == 0) {
+				continue;
+			}
+
+			if (get_vopstats(kc, ent->e_ksname,
+			    &ent->e_vs[CUR_INDEX], NULL) == 0) {
+				(*dfunc)(ent->e_name, &ent->e_vs[PREV_INDEX],
+				    &ent->e_vs[CUR_INDEX],
+				    dispflag_policy(printhdr, dispflag));
+				linesout++;
+				nentities_found++;
+			} else {
+				if (ent->e_type == ENTYPE_MNTPT) {
+					(void) printf(gettext(
+					    "<<mount point no longer "
+					    "available: %s>>\n"), ent->e_name);
+				} else if (ent->e_type == ENTYPE_FSTYPE) {
+					(void) printf(gettext(
+					    "<<file system module no longer "
+					    "loaded: %s>>\n"), ent->e_name);
+				} else {
+					(void) printf(gettext(
+					    "<<%s no longer available>>\n"),
+					    ent->e_name);
+				}
+				/* Disable this so it doesn't print again */
+				ent->e_ksname[0] = 0;
+			}
+			printhdr = 0;	/* Always shut this off */
+		}
+		BUMP_INDEX();	/* Bump the previous/current indices */
+
+		/*
+		 * If the entities we were observing are no longer there
+		 * (file system modules unloaded, file systems unmounted)
+		 * then we're done.
+		 */
+		if (nentities_found == 0)
+			break;
+	}
+
+	return (0);
+}
--- a/usr/src/pkgdefs/SUNWcsu/prototype_com	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/pkgdefs/SUNWcsu/prototype_com	Sat Feb 25 01:33:06 2006 -0800
@@ -127,6 +127,7 @@
 f none usr/bin/fmt 555 root bin
 f none usr/bin/fmtmsg 555 root bin
 f none usr/bin/fold 555 root bin
+f none usr/bin/fsstat 555 root bin
 f none usr/bin/geniconvtbl 555 root bin
 f none usr/bin/getconf 555 root bin
 f none usr/bin/getdev 555 root bin
--- a/usr/src/uts/common/fs/autofs/auto_vfsops.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/autofs/auto_vfsops.c	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -94,7 +93,7 @@
 	VFSDEF_VERSION,
 	"autofs",
 	autofs_init,
-	VSW_HASPROTO|VSW_CANRWRO|VSW_CANREMOUNT,
+	VSW_HASPROTO|VSW_CANRWRO|VSW_CANREMOUNT|VSW_STATS,
 	&auto_mntopts
 };
 
--- a/usr/src/uts/common/fs/hsfs/hsfs_vfsops.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/hsfs/hsfs_vfsops.c	Sat Feb 25 01:33:06 2006 -0800
@@ -19,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -125,7 +125,7 @@
 	VFSDEF_VERSION,
 	"hsfs",
 	hsfsinit,
-	VSW_HASPROTO,	/* We don't suppport remounting */
+	VSW_HASPROTO|VSW_STATS,	/* We don't suppport remounting */
 	&hsfs_proto_opttbl
 };
 
--- a/usr/src/uts/common/fs/lofs/lofs_vfsops.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/lofs/lofs_vfsops.c	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -59,7 +58,7 @@
 	VFSDEF_VERSION,
 	"lofs",
 	lofsinit,
-	VSW_HASPROTO,
+	VSW_HASPROTO|VSW_STATS,
 	&lofs_mntopts
 };
 
--- a/usr/src/uts/common/fs/mntfs/mntvfsops.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/mntfs/mntvfsops.c	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -68,7 +67,7 @@
 	VFSDEF_VERSION,
 	"mntfs",
 	mntinit,
-	VSW_HASPROTO,
+	VSW_HASPROTO|VSW_STATS,
 	&mnt_mntopts
 };
 
--- a/usr/src/uts/common/fs/nfs/nfs4_common.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/nfs/nfs4_common.c	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -53,7 +52,7 @@
 	VFSDEF_VERSION,
 	"nfs4",
 	nfs4init,
-	VSW_CANREMOUNT|VSW_NOTZONESAFE,
+	VSW_CANREMOUNT|VSW_NOTZONESAFE|VSW_STATS,
 	NULL
 };
 
--- a/usr/src/uts/common/fs/nfs/nfs_common.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/nfs/nfs_common.c	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -142,7 +141,7 @@
 	VFSDEF_VERSION,
 	"nfs",
 	nfsinit,
-	VSW_CANREMOUNT|VSW_NOTZONESAFE,
+	VSW_CANREMOUNT|VSW_NOTZONESAFE|VSW_STATS,
 	NULL
 };
 
@@ -159,7 +158,7 @@
 	VFSDEF_VERSION,
 	"nfs3",
 	nfs3init,
-	VSW_CANREMOUNT|VSW_NOTZONESAFE,
+	VSW_CANREMOUNT|VSW_NOTZONESAFE|VSW_STATS,
 	NULL
 };
 
--- a/usr/src/uts/common/fs/pcfs/pc_vfsops.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/pcfs/pc_vfsops.c	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -140,7 +139,7 @@
 	VFSDEF_VERSION,
 	"pcfs",
 	pcfsinit,
-	VSW_HASPROTO|VSW_CANREMOUNT,
+	VSW_HASPROTO|VSW_CANREMOUNT|VSW_STATS,
 	&pcfs_mntopts
 };
 
--- a/usr/src/uts/common/fs/proc/prvfsops.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/proc/prvfsops.c	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -72,7 +71,7 @@
 	VFSDEF_VERSION,
 	"proc",
 	prinit,
-	VSW_HASPROTO,
+	VSW_HASPROTO|VSW_STATS,
 	&proc_mntopts
 };
 
--- a/usr/src/uts/common/fs/tmpfs/tmp_vfsops.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/tmpfs/tmp_vfsops.c	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -79,7 +78,7 @@
 	VFSDEF_VERSION,
 	"tmpfs",
 	tmpfsinit,
-	VSW_HASPROTO,
+	VSW_HASPROTO|VSW_STATS,
 	&tmpfs_proto_opttbl
 };
 
--- a/usr/src/uts/common/fs/udfs/udf_vfsops.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/udfs/udf_vfsops.c	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -122,7 +121,7 @@
 	VFSDEF_VERSION,
 	"udfs",
 	udfinit,
-	VSW_HASPROTO|VSW_CANREMOUNT,
+	VSW_HASPROTO|VSW_CANREMOUNT|VSW_STATS,
 	&udfs_mntopts
 };
 
--- a/usr/src/uts/common/fs/ufs/ufs_vfsops.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/ufs/ufs_vfsops.c	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -197,7 +196,7 @@
 	VFSDEF_VERSION,
 	"ufs",
 	ufsinit,
-	VSW_HASPROTO|VSW_CANREMOUNT,
+	VSW_HASPROTO|VSW_CANREMOUNT|VSW_STATS,
 	&ufs_mntopts
 };
 
--- a/usr/src/uts/common/fs/vfs.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/vfs.c	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -494,6 +493,10 @@
 	vfsp->vfs_mntopts.mo_list = NULL;
 	vfsp->vfs_femhead = NULL;
 	vfsp->vfs_zone = NULL;
+	/*
+	 * Note: Don't initialize vfs_vskap, vfs_fstypevsp since it
+	 * could be a problem for unbundled file systems.
+	 */
 	vfs_setops((vfsp), (op));
 	sema_init(&vfsp->vfs_reflock, 1, NULL, SEMA_DEFAULT, NULL);
 }
@@ -657,6 +660,8 @@
 	struct vnode	*rvp = NULL;
 	char		*path;
 	size_t		plen;
+	struct vfssw	*vswp;
+	extern void setup_vopstats(vfs_t *);
 
 	rw_init(&vfssw_lock, NULL, RW_DEFAULT, NULL);
 	rw_init(&vfslist, NULL, RW_DEFAULT, NULL);
@@ -705,6 +710,16 @@
 	 */
 	clboot_mountroot();
 
+	/* Now that we're all done with the root FS, set up its vopstats */
+	if ((vswp = vfs_getvfsswbyvfsops(vfs_getops(rootvfs))) != NULL) {
+		/* Set flag for statistics collection */
+		if (vswp->vsw_flag & VSW_STATS) {
+			rootvfs->vfs_flag |= VFS_STATS;
+		}
+		vfs_unrefvfssw(vswp);
+	}
+	setup_vopstats(rootvfs);
+
 	/*
 	 * Mount /devices, /system/contract, /etc/mnttab, /etc/svc/volatile,
 	 * /system/object, and /proc.
@@ -848,6 +863,7 @@
 	char		*resource = NULL, *mountpt = NULL;
 	refstr_t	*oldresource, *oldmntpt;
 	struct pathname	pn, rpn;
+	extern void setup_vopstats(vfs_t *);
 
 	/*
 	 * The v_flag value for the mount point vp is permanently set
@@ -1390,16 +1406,27 @@
 				    optlen, NULL);
 			}
 		}
+
+		/* Set flag for statistics collection */
+		if (vswp->vsw_flag & VSW_STATS) {
+			vfsp->vfs_flag |= VFS_STATS;
+		}
+
 		vfs_unlock(vfsp);
 	}
 	mount_completed();
 	if (splice)
 		vn_vfsunlock(vp);
 
-	/*
-	 * Return vfsp to caller.
-	 */
 	if ((error == 0) && (copyout_error == 0)) {
+		/*
+		 * If this isn't a remount, set up the vopstats before
+		 * anyone can touch this
+		 */
+		if (!remount)
+			setup_vopstats(vfsp);
+
+		/* Return vfsp to caller. */
 		*vfspp = vfsp;
 	}
 errout:
@@ -2458,6 +2485,7 @@
 {
 	vnode_t *coveredvp;
 	int error;
+	extern void teardown_vopstats(vfs_t *);
 
 	/*
 	 * Get covered vnode. This will be NULL if the vfs is not linked
@@ -2487,6 +2515,7 @@
 		if (coveredvp != NULL)
 			vn_vfsunlock(coveredvp);
 	} else if (coveredvp != NULL) {
+		teardown_vopstats(vfsp);
 		/*
 		 * vfs_remove() will do a VN_RELE(vfsp->vfs_vnodecovered)
 		 * when it frees vfsp so we do a VN_HOLD() so we can
@@ -2497,6 +2526,7 @@
 		vn_vfsunlock(coveredvp);
 		VN_RELE(coveredvp);
 	} else {
+		teardown_vopstats(vfsp);
 		/*
 		 * Release the reference to vfs that is not linked
 		 * into the name space.
@@ -3648,6 +3678,8 @@
 {
 	struct vfssw *vswp;
 	int error;
+	extern void vopstats_startup();
+	extern void setup_vopstats(vfs_t *);
 
 	static const fs_operation_def_t EIO_vfsops_template[] = {
 		VFSNAME_MOUNT,		vfs_EIO,
@@ -3702,6 +3734,9 @@
 			(*vswp->vsw_init)(vswp - vfssw, vswp->vsw_name);
 		RUNLOCK_VFSSW();
 	}
+
+	vopstats_startup();
+	setup_vopstats(&EIO_vfs);
 }
 
 /*
--- a/usr/src/uts/common/fs/vnode.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/vnode.c	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -76,6 +75,70 @@
 int nfs_global_client_only;
 
 /*
+ * Array of vopstats_t for per-FS-type vopstats.  This array has the same
+ * number of entries as and parallel to the vfssw table.  (Arguably, it could
+ * be part of the vfssw table.)  Once it's initialized, it's accessed using
+ * the same fstype index that is used to index into the vfssw table.
+ */
+vopstats_t **vopstats_fstype;
+
+/* vopstats initialization template used for fast initialization via bcopy() */
+static vopstats_t *vs_templatep;
+
+/* Kmem cache handle for vsk_anchor_t allocations */
+kmem_cache_t *vsk_anchor_cache;
+
+/*
+ * Root of AVL tree for the kstats associated with vopstats.  Lock protects
+ * updates to vsktat_tree.
+ */
+avl_tree_t	vskstat_tree;
+kmutex_t	vskstat_tree_lock;
+
+/* Global variable which enables/disables the vopstats collection */
+int vopstats_enabled = 1;
+
+/*
+ * The following is the common set of actions needed to update the
+ * vopstats structure from a vnode op.  Both VOPSTATS_UPDATE() and
+ * VOPSTATS_UPDATE_IO() do almost the same thing, except for the
+ * recording of the bytes transferred.  Since the code is similar
+ * but small, it is nearly a duplicate.  Consequently any changes
+ * to one may need to be reflected in the other.
+ * Rundown of the variables:
+ * vp - Pointer to the vnode
+ * counter - Partial name structure member to update in vopstats for counts
+ * bytecounter - Partial name structure member to update in vopstats for bytes
+ * bytesval - Value to update in vopstats for bytes
+ * fstype - Index into vsanchor_fstype[], same as index into vfssw[]
+ * vsp - Pointer to vopstats structure (either in vfs or vsanchor_fstype[i])
+ */
+
+#define	VOPSTATS_UPDATE(vp, counter) {					\
+	vfs_t *vfsp = (vp)->v_vfsp;					\
+	if (vfsp && (vfsp->vfs_flag & VFS_STATS) && (vp)->v_type != VBAD) { \
+		vopstats_t *vsp = &vfsp->vfs_vopstats;			\
+		vsp->counter.value.ui64++;				\
+		if ((vsp = vfsp->vfs_fstypevsp) != NULL) {		\
+			vsp->counter.value.ui64++;			\
+		}							\
+	}								\
+}
+
+#define	VOPSTATS_UPDATE_IO(vp, counter, bytecounter, bytesval) {	\
+	vfs_t *vfsp = (vp)->v_vfsp;					\
+	if (vfsp && (vfsp->vfs_flag & VFS_STATS) && (vp)->v_type != VBAD) { \
+		vopstats_t *vsp = &vfsp->vfs_vopstats;			\
+		vsp->counter.value.ui64++;				\
+		vsp->bytecounter.value.ui64 += bytesval;		\
+		if ((vsp = vfsp->vfs_fstypevsp) != NULL) {		\
+			vsp->counter.value.ui64++;			\
+			vsp->bytecounter.value.ui64 += bytesval;	\
+		}							\
+	}								\
+}
+
+/*
  * Convert stat(2) formats to vnode types and vice versa.  (Knows about
  * numerical order of S_IFMT and vnode types.)
  */
@@ -241,6 +304,329 @@
 	NULL, 0, NULL, NULL
 };
 
+/*
+ * Used by the AVL routines to compare two vsk_anchor_t structures in the tree.
+ * We use the f_fsid reported by VFS_STATVFS() since we use that for the
+ * kstat name.
+ */
+static int
+vska_compar(const void *n1, const void *n2)
+{
+	int ret;
+	ulong_t p1 = ((vsk_anchor_t *)n1)->vsk_fsid;
+	ulong_t p2 = ((vsk_anchor_t *)n2)->vsk_fsid;
+
+	if (p1 < p2) {
+		ret = -1;
+	} else if (p1 > p2) {
+		ret = 1;
+	} else {
+		ret = 0;
+	}
+
+	return (ret);
+}
+
+/*
+ * Used to create a single template which will be bcopy()ed to a newly
+ * allocated vsanchor_combo_t structure in new_vsanchor(), below.
+ */
+static vopstats_t *
+create_vopstats_template()
+{
+	vopstats_t		*vsp;
+
+	vsp = kmem_alloc(sizeof (vopstats_t), KM_SLEEP);
+	bzero(vsp, sizeof (*vsp));	/* Start fresh */
+
+	/* VOP_OPEN */
+	kstat_named_init(&vsp->nopen, "nopen", KSTAT_DATA_UINT64);
+	/* VOP_CLOSE */
+	kstat_named_init(&vsp->nclose, "nclose", KSTAT_DATA_UINT64);
+	/* VOP_READ I/O */
+	kstat_named_init(&vsp->nread, "nread", KSTAT_DATA_UINT64);
+	kstat_named_init(&vsp->read_bytes, "read_bytes", KSTAT_DATA_UINT64);
+	/* VOP_WRITE I/O */
+	kstat_named_init(&vsp->nwrite, "nwrite", KSTAT_DATA_UINT64);
+	kstat_named_init(&vsp->write_bytes, "write_bytes", KSTAT_DATA_UINT64);
+	/* VOP_IOCTL */
+	kstat_named_init(&vsp->nioctl, "nioctl", KSTAT_DATA_UINT64);
+	/* VOP_SETFL */
+	kstat_named_init(&vsp->nsetfl, "nsetfl", KSTAT_DATA_UINT64);
+	/* VOP_GETATTR */
+	kstat_named_init(&vsp->ngetattr, "ngetattr", KSTAT_DATA_UINT64);
+	/* VOP_SETATTR */
+	kstat_named_init(&vsp->nsetattr, "nsetattr", KSTAT_DATA_UINT64);
+	/* VOP_ACCESS */
+	kstat_named_init(&vsp->naccess, "naccess", KSTAT_DATA_UINT64);
+	/* VOP_LOOKUP */
+	kstat_named_init(&vsp->nlookup, "nlookup", KSTAT_DATA_UINT64);
+	/* VOP_CREATE */
+	kstat_named_init(&vsp->ncreate, "ncreate", KSTAT_DATA_UINT64);
+	/* VOP_REMOVE */
+	kstat_named_init(&vsp->nremove, "nremove", KSTAT_DATA_UINT64);
+	/* VOP_LINK */
+	kstat_named_init(&vsp->nlink, "nlink", KSTAT_DATA_UINT64);
+	/* VOP_RENAME */
+	kstat_named_init(&vsp->nrename, "nrename", KSTAT_DATA_UINT64);
+	/* VOP_MKDIR */
+	kstat_named_init(&vsp->nmkdir, "nmkdir", KSTAT_DATA_UINT64);
+	/* VOP_RMDIR */
+	kstat_named_init(&vsp->nrmdir, "nrmdir", KSTAT_DATA_UINT64);
+	/* VOP_READDIR I/O */
+	kstat_named_init(&vsp->nreaddir, "nreaddir", KSTAT_DATA_UINT64);
+	kstat_named_init(&vsp->readdir_bytes, "readdir_bytes",
+	    KSTAT_DATA_UINT64);
+	/* VOP_SYMLINK */
+	kstat_named_init(&vsp->nsymlink, "nsymlink", KSTAT_DATA_UINT64);
+	/* VOP_READLINK */
+	kstat_named_init(&vsp->nreadlink, "nreadlink", KSTAT_DATA_UINT64);
+	/* VOP_FSYNC */
+	kstat_named_init(&vsp->nfsync, "nfsync", KSTAT_DATA_UINT64);
+	/* VOP_INACTIVE */
+	kstat_named_init(&vsp->ninactive, "ninactive", KSTAT_DATA_UINT64);
+	/* VOP_FID */
+	kstat_named_init(&vsp->nfid, "nfid", KSTAT_DATA_UINT64);
+	/* VOP_RWLOCK */
+	kstat_named_init(&vsp->nrwlock, "nrwlock", KSTAT_DATA_UINT64);
+	/* VOP_RWUNLOCK */
+	kstat_named_init(&vsp->nrwunlock, "nrwunlock", KSTAT_DATA_UINT64);
+	/* VOP_SEEK */
+	kstat_named_init(&vsp->nseek, "nseek", KSTAT_DATA_UINT64);
+	/* VOP_CMP */
+	kstat_named_init(&vsp->ncmp, "ncmp", KSTAT_DATA_UINT64);
+	/* VOP_FRLOCK */
+	kstat_named_init(&vsp->nfrlock, "nfrlock", KSTAT_DATA_UINT64);
+	/* VOP_SPACE */
+	kstat_named_init(&vsp->nspace, "nspace", KSTAT_DATA_UINT64);
+	/* VOP_REALVP */
+	kstat_named_init(&vsp->nrealvp, "nrealvp", KSTAT_DATA_UINT64);
+	/* VOP_GETPAGE */
+	kstat_named_init(&vsp->ngetpage, "ngetpage", KSTAT_DATA_UINT64);
+	/* VOP_PUTPAGE */
+	kstat_named_init(&vsp->nputpage, "nputpage", KSTAT_DATA_UINT64);
+	/* VOP_MAP */
+	kstat_named_init(&vsp->nmap, "nmap", KSTAT_DATA_UINT64);
+	/* VOP_ADDMAP */
+	kstat_named_init(&vsp->naddmap, "naddmap", KSTAT_DATA_UINT64);
+	/* VOP_DELMAP */
+	kstat_named_init(&vsp->ndelmap, "ndelmap", KSTAT_DATA_UINT64);
+	/* VOP_POLL */
+	kstat_named_init(&vsp->npoll, "npoll", KSTAT_DATA_UINT64);
+	/* VOP_DUMP */
+	kstat_named_init(&vsp->ndump, "ndump", KSTAT_DATA_UINT64);
+	/* VOP_PATHCONF */
+	kstat_named_init(&vsp->npathconf, "npathconf", KSTAT_DATA_UINT64);
+	/* VOP_PAGEIO */
+	kstat_named_init(&vsp->npageio, "npageio", KSTAT_DATA_UINT64);
+	/* VOP_DUMPCTL */
+	kstat_named_init(&vsp->ndumpctl, "ndumpctl", KSTAT_DATA_UINT64);
+	/* VOP_DISPOSE */
+	kstat_named_init(&vsp->ndispose, "ndispose", KSTAT_DATA_UINT64);
+	/* VOP_SETSECATTR */
+	kstat_named_init(&vsp->nsetsecattr, "nsetsecattr", KSTAT_DATA_UINT64);
+	/* VOP_GETSECATTR */
+	kstat_named_init(&vsp->ngetsecattr, "ngetsecattr", KSTAT_DATA_UINT64);
+	/* VOP_SHRLOCK */
+	kstat_named_init(&vsp->nshrlock, "nshrlock", KSTAT_DATA_UINT64);
+	/* VOP_VNEVENT */
+	kstat_named_init(&vsp->nvnevent, "nvnevent", KSTAT_DATA_UINT64);
+
+	return (vsp);
+}
+
+/*
+ * Creates a kstat structure associated with a vopstats structure.
+ */
+kstat_t *
+new_vskstat(char *ksname, vopstats_t *vsp)
+{
+	kstat_t		*ksp;
+
+	if (!vopstats_enabled) {
+		return (NULL);
+	}
+
+	ksp = kstat_create("unix", 0, ksname, "misc", KSTAT_TYPE_NAMED,
+	    sizeof (vopstats_t)/sizeof (kstat_named_t),
+	    KSTAT_FLAG_VIRTUAL|KSTAT_FLAG_WRITABLE);
+	if (ksp) {
+		ksp->ks_data = vsp;
+		kstat_install(ksp);
+	}
+
+	return (ksp);
+}
+
+/*
+ * Called from vfsinit() to initialize the support mechanisms for vopstats
+ */
+void
+vopstats_startup()
+{
+	if (!vopstats_enabled)
+		return;
+
+	/*
+	 * Creates the AVL tree which holds per-vfs vopstat anchors.  This
+	 * is necessary since we need to check if a kstat exists before we
+	 * attempt to create it.  Also, initialize its lock.
+	 */
+	avl_create(&vskstat_tree, vska_compar, sizeof (vsk_anchor_t),
+	    offsetof(vsk_anchor_t, vsk_node));
+	mutex_init(&vskstat_tree_lock, NULL, MUTEX_DEFAULT, NULL);
+
+	vsk_anchor_cache = kmem_cache_create("vsk_anchor_cache",
+	    sizeof (vsk_anchor_t), sizeof (uintptr_t), NULL, NULL, NULL,
+	    NULL, NULL, 0);
+
+	/*
+	 * Set up the array of pointers for the vopstats-by-FS-type.
+	 * The entries will be allocated/initialized as each file system
+	 * goes through modload/mod_installfs.
+	 */
+	vopstats_fstype = (vopstats_t **)kmem_zalloc(
+	    (sizeof (vopstats_t *) * nfstype), KM_SLEEP);
+
+	/* Set up the global vopstats initialization template */
+	vs_templatep = create_vopstats_template();
+}
+
+/*
+ * We need to have the all of the counters zeroed.
+ * The initialization of the vopstats_t includes on the order of
+ * 50 calls to kstat_named_init().  Rather that do that on every call,
+ * we do it once in a template (vs_templatep) then bcopy it over.
+ */
+void
+initialize_vopstats(vopstats_t *vsp)
+{
+	if (vsp == NULL)
+		return;
+
+	bcopy(vs_templatep, vsp, sizeof (vopstats_t));
+}
+
+/*
+ * Create and initialize the vopstat structure for a vfs. Also, generate
+ * a kstat name, create the kstat structure, and associate it with the
+ * vfs' vopstats.  This must only be called from mount.
+ */
+void
+setup_vopstats(vfs_t *vfsp)
+{
+	int		fstype = 0;		/* Index into vfssw[] */
+	char		kstatstr[KSTAT_STRLEN]; /* kstat name for vopstats */
+	statvfs64_t	statvfsbuf;		/* Needed to find f_fsid */
+	vsk_anchor_t	*vskp;			/* vfs <--> kstat anchor */
+	vfsops_t	*vfsops;		/* vfs operations vector */
+	vfssw_t		*vswp;			/* Ptr into vfssw[] table */
+	kstat_t		*ksp;			/* Ptr to new kstat */
+	avl_index_t	where;			/* Location in the AVL tree */
+
+	if (vfsp == NULL || (vfsp->vfs_flag & VFS_STATS) == 0 ||
+	    !vopstats_enabled)
+		return;
+
+	initialize_vopstats(&vfsp->vfs_vopstats);
+
+	/*
+	 * Set up the fstype.  We go to so much trouble because all versions
+	 * of NFS use the same fstype in their vfs even though they have
+	 * distinct entries in the vfssw[] table.
+	 */
+	if (vfsp && (vfsops = vfs_getops(vfsp)) != NULL) {
+		vswp = vfs_getvfsswbyvfsops(vfsops);
+		/* A special vfs (e.g., EIO_vfs) may not have an entry */
+		if (vswp) {
+			fstype = vswp - vfssw;	/* Gets us the index */
+			vfs_unrefvfssw(vswp);	/* Must release reference */
+		}
+	} else {
+		fstype = vfsp->vfs_fstype;
+	}
+
+	/*
+	 * Point to the per-fstype vopstats. The only valid values are
+	 * non-zero positive values less than the number of vfssw[] table
+	 * entries.
+	 */
+	if (fstype > 0 && fstype < nfstype) {
+		vfsp->vfs_fstypevsp = vopstats_fstype[fstype];
+	} else {
+		/* Otherwise, never attempt to update stats by fstype */
+		vfsp->vfs_fstypevsp = NULL;
+	}
+
+	/* Need to get the fsid to build a kstat name */
+	if (VFS_STATVFS(vfsp, &statvfsbuf) == 0) {
+		/* Create a name for our kstats based on fsid */
+		(void) snprintf(kstatstr, KSTAT_STRLEN, "%s%lx",
+		    VOPSTATS_STR, statvfsbuf.f_fsid);
+
+		/* Allocate and initialize the vsk_anchor_t */
+		vskp = kmem_cache_alloc(vsk_anchor_cache, KM_SLEEP);
+		bzero(vskp, sizeof (*vskp));
+		vskp->vsk_fsid = statvfsbuf.f_fsid;
+		vfsp->vfs_vskap = vskp;
+
+		mutex_enter(&vskstat_tree_lock);
+		if (avl_find(&vskstat_tree, vskp, &where) == NULL) {
+			avl_insert(&vskstat_tree, vskp, where);
+			mutex_exit(&vskstat_tree_lock);
+
+			/*
+			 * Now that we've got the anchor in the AVL
+			 * tree, we can create the kstat.
+			 */
+			ksp = new_vskstat(kstatstr, &vfsp->vfs_vopstats);
+			if (ksp) {
+				vskp->vsk_ksp = ksp;
+			}
+		} else {
+			/* Oops, found one! Release memory and lock. */
+			mutex_exit(&vskstat_tree_lock);
+			vfsp->vfs_vskap = NULL;
+			kmem_cache_free(vsk_anchor_cache, vskp);
+		}
+	}
+}
+
+/*
+ * We're in the process of tearing down the vfs and need to cleanup
+ * the data structures associated with the vopstats. Must only be called
+ * from dounmount().
+ */
+void
+teardown_vopstats(vfs_t *vfsp)
+{
+	vsk_anchor_t	*vskap;
+	avl_index_t	where;
+
+	if (vfsp == NULL || (vfsp->vfs_flag & VFS_STATS) == 0 ||
+	    !vopstats_enabled)
+		return;
+
+	/* This is a safe check since VFS_STATS must be set (see above) */
+	if ((vskap = vfsp->vfs_vskap) == NULL)
+		return;
+
+	/* Whack the pointer right away */
+	vfsp->vfs_vskap = NULL;
+
+	/* Lock the tree, remove the node, and delete the kstat */
+	mutex_enter(&vskstat_tree_lock);
+	if (avl_find(&vskstat_tree, vskap, &where)) {
+		avl_remove(&vskstat_tree, vskap);
+	}
+
+	if (vskap->vsk_ksp) {
+		kstat_delete(vskap->vsk_ksp);
+	}
+	mutex_exit(&vskstat_tree_lock);
+
+	kmem_cache_free(vsk_anchor_cache, vskap);
+}
 
 /*
  * Read or write a vnode.  Called from kernel code.
@@ -2307,6 +2693,7 @@
 		 * Use the saved vp just in case the vnode ptr got trashed
 		 * by the error.
 		 */
+		VOPSTATS_UPDATE(vp, nopen);
 		if ((vp->v_type == VREG) && (mode & FREAD))
 			atomic_add_32(&(vp->v_rdcnt), -1);
 		if ((vp->v_type == VREG) && (mode & FWRITE))
@@ -2320,7 +2707,7 @@
 		 * casing each filesystem. Adjust the vnode counts to
 		 * reflect the vnode switch.
 		 */
-
+		VOPSTATS_UPDATE(*vpp, nopen);
 		if (*vpp != vp && *vpp != NULL) {
 			vn_copypath(vp, *vpp);
 			if (((*vpp)->v_type == VREG) && (mode & FREAD))
@@ -2345,8 +2732,10 @@
 	offset_t offset,
 	cred_t *cr)
 {
-	int error;
-	error = (*(vp)->v_op->vop_close)(vp, flag, count, offset, cr);
+	int err;
+
+	err = (*(vp)->v_op->vop_close)(vp, flag, count, offset, cr);
+	VOPSTATS_UPDATE(vp, nclose);
 	/*
 	 * Check passed in count to handle possible dups. Vnode counts are only
 	 * kept on regular files
@@ -2361,7 +2750,7 @@
 			atomic_add_32(&(vp->v_wrcnt), -1);
 		}
 	}
-	return (error);
+	return (err);
 }
 
 int
@@ -2372,7 +2761,13 @@
 	cred_t *cr,
 	struct caller_context *ct)
 {
-	return (*(vp)->v_op->vop_read)(vp, uiop, ioflag, cr, ct);
+	int	err;
+	ssize_t	resid_start = uiop->uio_resid;
+
+	err = (*(vp)->v_op->vop_read)(vp, uiop, ioflag, cr, ct);
+	VOPSTATS_UPDATE_IO(vp, nread,
+	    read_bytes, (resid_start - uiop->uio_resid));
+	return (err);
 }
 
 int
@@ -2383,7 +2778,13 @@
 	cred_t *cr,
 	struct caller_context *ct)
 {
-	return (*(vp)->v_op->vop_write)(vp, uiop, ioflag, cr, ct);
+	int	err;
+	ssize_t	resid_start = uiop->uio_resid;
+
+	err = (*(vp)->v_op->vop_write)(vp, uiop, ioflag, cr, ct);
+	VOPSTATS_UPDATE_IO(vp, nwrite,
+	    write_bytes, (resid_start - uiop->uio_resid));
+	return (err);
 }
 
 int
@@ -2395,7 +2796,11 @@
 	cred_t *cr,
 	int *rvalp)
 {
-	return (*(vp)->v_op->vop_ioctl)(vp, cmd, arg, flag, cr, rvalp);
+	int	err;
+
+	err = (*(vp)->v_op->vop_ioctl)(vp, cmd, arg, flag, cr, rvalp);
+	VOPSTATS_UPDATE(vp, nioctl);
+	return (err);
 }
 
 int
@@ -2405,7 +2810,11 @@
 	int nflags,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_setfl)(vp, oflags, nflags, cr);
+	int	err;
+
+	err = (*(vp)->v_op->vop_setfl)(vp, oflags, nflags, cr);
+	VOPSTATS_UPDATE(vp, nsetfl);
+	return (err);
 }
 
 int
@@ -2415,7 +2824,11 @@
 	int flags,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_getattr)(vp, vap, flags, cr);
+	int	err;
+
+	err = (*(vp)->v_op->vop_getattr)(vp, vap, flags, cr);
+	VOPSTATS_UPDATE(vp, ngetattr);
+	return (err);
 }
 
 int
@@ -2426,7 +2839,11 @@
 	cred_t *cr,
 	caller_context_t *ct)
 {
-	return (*(vp)->v_op->vop_setattr)(vp, vap, flags, cr, ct);
+	int	err;
+
+	err = (*(vp)->v_op->vop_setattr)(vp, vap, flags, cr, ct);
+	VOPSTATS_UPDATE(vp, nsetattr);
+	return (err);
 }
 
 int
@@ -2436,7 +2853,11 @@
 	int flags,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_access)(vp, mode, flags, cr);
+	int	err;
+
+	err = (*(vp)->v_op->vop_access)(vp, mode, flags, cr);
+	VOPSTATS_UPDATE(vp, naccess);
+	return (err);
 }
 
 int
@@ -2452,8 +2873,12 @@
 	int ret;
 
 	ret = (*(dvp)->v_op->vop_lookup)(dvp, nm, vpp, pnp, flags, rdir, cr);
-	if (ret == 0 && *vpp && (*vpp)->v_path == NULL)
-		vn_setpath(rootdir, dvp, *vpp, nm, strlen(nm));
+	if (ret == 0 && *vpp) {
+		VOPSTATS_UPDATE(*vpp, nlookup);
+		if ((*vpp)->v_path == NULL) {
+			vn_setpath(rootdir, dvp, *vpp, nm, strlen(nm));
+		}
+	}
 
 	return (ret);
 }
@@ -2473,8 +2898,12 @@
 
 	ret = (*(dvp)->v_op->vop_create)
 				(dvp, name, vap, excl, mode, vpp, cr, flag);
-	if (ret == 0 && *vpp && (*vpp)->v_path == NULL)
-		vn_setpath(rootdir, dvp, *vpp, name, strlen(name));
+	if (ret == 0 && *vpp) {
+		VOPSTATS_UPDATE(*vpp, ncreate);
+		if ((*vpp)->v_path == NULL) {
+			vn_setpath(rootdir, dvp, *vpp, name, strlen(name));
+		}
+	}
 
 	return (ret);
 }
@@ -2485,7 +2914,11 @@
 	char *nm,
 	cred_t *cr)
 {
-	return (*(dvp)->v_op->vop_remove)(dvp, nm, cr);
+	int	err;
+
+	err = (*(dvp)->v_op->vop_remove)(dvp, nm, cr);
+	VOPSTATS_UPDATE(dvp, nremove);
+	return (err);
 }
 
 int
@@ -2495,7 +2928,11 @@
 	char *tnm,
 	cred_t *cr)
 {
-	return (*(tdvp)->v_op->vop_link)(tdvp, svp, tnm, cr);
+	int	err;
+
+	err = (*(tdvp)->v_op->vop_link)(tdvp, svp, tnm, cr);
+	VOPSTATS_UPDATE(tdvp, nlink);
+	return (err);
 }
 
 int
@@ -2506,7 +2943,11 @@
 	char *tnm,
 	cred_t *cr)
 {
-	return (*(sdvp)->v_op->vop_rename)(sdvp, snm, tdvp, tnm, cr);
+	int	err;
+
+	err = (*(sdvp)->v_op->vop_rename)(sdvp, snm, tdvp, tnm, cr);
+	VOPSTATS_UPDATE(sdvp, nrename);
+	return (err);
 }
 
 int
@@ -2520,8 +2961,13 @@
 	int ret;
 
 	ret = (*(dvp)->v_op->vop_mkdir)(dvp, dirname, vap, vpp, cr);
-	if (ret == 0 && *vpp && (*vpp)->v_path == NULL)
-		vn_setpath(rootdir, dvp, *vpp, dirname, strlen(dirname));
+	if (ret == 0 && *vpp) {
+		VOPSTATS_UPDATE(*vpp, nmkdir);
+		if ((*vpp)->v_path == NULL) {
+			vn_setpath(rootdir, dvp, *vpp, dirname,
+			    strlen(dirname));
+		}
+	}
 
 	return (ret);
 }
@@ -2533,7 +2979,11 @@
 	vnode_t *cdir,
 	cred_t *cr)
 {
-	return (*(dvp)->v_op->vop_rmdir)(dvp, nm, cdir, cr);
+	int	err;
+
+	err = (*(dvp)->v_op->vop_rmdir)(dvp, nm, cdir, cr);
+	VOPSTATS_UPDATE(dvp, nrmdir);
+	return (err);
 }
 
 int
@@ -2543,7 +2993,13 @@
 	cred_t *cr,
 	int *eofp)
 {
-	return (*(vp)->v_op->vop_readdir)(vp, uiop, cr, eofp);
+	int	err;
+	ssize_t	resid_start = uiop->uio_resid;
+
+	err = (*(vp)->v_op->vop_readdir)(vp, uiop, cr, eofp);
+	VOPSTATS_UPDATE_IO(vp, nreaddir,
+	    readdir_bytes, (resid_start - uiop->uio_resid));
+	return (err);
 }
 
 int
@@ -2554,7 +3010,11 @@
 	char *target,
 	cred_t *cr)
 {
-	return (*(dvp)->v_op->vop_symlink) (dvp, linkname, vap, target, cr);
+	int	err;
+
+	err = (*(dvp)->v_op->vop_symlink) (dvp, linkname, vap, target, cr);
+	VOPSTATS_UPDATE(dvp, nsymlink);
+	return (err);
 }
 
 int
@@ -2563,7 +3023,11 @@
 	uio_t *uiop,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_readlink)(vp, uiop, cr);
+	int	err;
+
+	err = (*(vp)->v_op->vop_readlink)(vp, uiop, cr);
+	VOPSTATS_UPDATE(vp, nreadlink);
+	return (err);
 }
 
 int
@@ -2572,7 +3036,11 @@
 	int syncflag,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_fsync)(vp, syncflag, cr);
+	int	err;
+
+	err = (*(vp)->v_op->vop_fsync)(vp, syncflag, cr);
+	VOPSTATS_UPDATE(vp, nfsync);
+	return (err);
 }
 
 void
@@ -2580,6 +3048,8 @@
 	vnode_t *vp,
 	cred_t *cr)
 {
+	/* Need to update stats before vop call since we may lose the vnode */
+	VOPSTATS_UPDATE(vp, ninactive);
 	(*(vp)->v_op->vop_inactive)(vp, cr);
 }
 
@@ -2588,7 +3058,11 @@
 	vnode_t *vp,
 	fid_t *fidp)
 {
-	return (*(vp)->v_op->vop_fid)(vp, fidp);
+	int	err;
+
+	err = (*(vp)->v_op->vop_fid)(vp, fidp);
+	VOPSTATS_UPDATE(vp, nfid);
+	return (err);
 }
 
 int
@@ -2597,7 +3071,11 @@
 	int write_lock,
 	caller_context_t *ct)
 {
-	return ((*(vp)->v_op->vop_rwlock)(vp, write_lock, ct));
+	int	ret;
+
+	ret = ((*(vp)->v_op->vop_rwlock)(vp, write_lock, ct));
+	VOPSTATS_UPDATE(vp, nrwlock);
+	return (ret);
 }
 
 void
@@ -2607,6 +3085,7 @@
 	caller_context_t *ct)
 {
 	(*(vp)->v_op->vop_rwunlock)(vp, write_lock, ct);
+	VOPSTATS_UPDATE(vp, nrwunlock);
 }
 
 int
@@ -2615,7 +3094,11 @@
 	offset_t ooff,
 	offset_t *noffp)
 {
-	return (*(vp)->v_op->vop_seek)(vp, ooff, noffp);
+	int	err;
+
+	err = (*(vp)->v_op->vop_seek)(vp, ooff, noffp);
+	VOPSTATS_UPDATE(vp, nseek);
+	return (err);
 }
 
 int
@@ -2623,7 +3106,11 @@
 	vnode_t *vp1,
 	vnode_t *vp2)
 {
-	return (*(vp1)->v_op->vop_cmp)(vp1, vp2);
+	int	err;
+
+	err = (*(vp1)->v_op->vop_cmp)(vp1, vp2);
+	VOPSTATS_UPDATE(vp1, ncmp);
+	return (err);
 }
 
 int
@@ -2636,8 +3123,12 @@
 	struct flk_callback *flk_cbp,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_frlock)
+	int	err;
+
+	err = (*(vp)->v_op->vop_frlock)
 				(vp, cmd, bfp, flag, offset, flk_cbp, cr);
+	VOPSTATS_UPDATE(vp, nfrlock);
+	return (err);
 }
 
 int
@@ -2650,7 +3141,11 @@
 	cred_t *cr,
 	caller_context_t *ct)
 {
-	return (*(vp)->v_op->vop_space)(vp, cmd, bfp, flag, offset, cr, ct);
+	int	err;
+
+	err = (*(vp)->v_op->vop_space)(vp, cmd, bfp, flag, offset, cr, ct);
+	VOPSTATS_UPDATE(vp, nspace);
+	return (err);
 }
 
 int
@@ -2658,7 +3153,11 @@
 	vnode_t *vp,
 	vnode_t **vpp)
 {
-	return (*(vp)->v_op->vop_realvp)(vp, vpp);
+	int	err;
+
+	err = (*(vp)->v_op->vop_realvp)(vp, vpp);
+	VOPSTATS_UPDATE(vp, nrealvp);
+	return (err);
 }
 
 int
@@ -2674,8 +3173,12 @@
 	enum seg_rw rw,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_getpage)
+	int	err;
+
+	err = (*(vp)->v_op->vop_getpage)
 			(vp, off, len, protp, plarr, plsz, seg, addr, rw, cr);
+	VOPSTATS_UPDATE(vp, ngetpage);
+	return (err);
 }
 
 int
@@ -2686,7 +3189,11 @@
 	int flags,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_putpage)(vp, off, len, flags, cr);
+	int	err;
+
+	err = (*(vp)->v_op->vop_putpage)(vp, off, len, flags, cr);
+	VOPSTATS_UPDATE(vp, nputpage);
+	return (err);
 }
 
 int
@@ -2701,8 +3208,12 @@
 	uint_t flags,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_map)
+	int	err;
+
+	err = (*(vp)->v_op->vop_map)
 			(vp, off, as, addrp, len, prot, maxprot, flags, cr);
+	VOPSTATS_UPDATE(vp, nmap);
+	return (err);
 }
 
 int
@@ -2748,6 +3259,7 @@
 					(int64_t)delta);
 		}
 	}
+	VOPSTATS_UPDATE(vp, naddmap);
 	return (error);
 }
 
@@ -2798,6 +3310,7 @@
 					(int64_t)(-delta));
 		}
 	}
+	VOPSTATS_UPDATE(vp, ndelmap);
 	return (error);
 }
 
@@ -2810,7 +3323,11 @@
 	short *reventsp,
 	struct pollhead **phpp)
 {
-	return (*(vp)->v_op->vop_poll)(vp, events, anyyet, reventsp, phpp);
+	int	err;
+
+	err = (*(vp)->v_op->vop_poll)(vp, events, anyyet, reventsp, phpp);
+	VOPSTATS_UPDATE(vp, npoll);
+	return (err);
 }
 
 int
@@ -2820,7 +3337,11 @@
 	int lbdn,
 	int dblks)
 {
-	return (*(vp)->v_op->vop_dump)(vp, addr, lbdn, dblks);
+	int	err;
+
+	err = (*(vp)->v_op->vop_dump)(vp, addr, lbdn, dblks);
+	VOPSTATS_UPDATE(vp, ndump);
+	return (err);
 }
 
 int
@@ -2830,7 +3351,11 @@
 	ulong_t *valp,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_pathconf)(vp, cmd, valp, cr);
+	int	err;
+
+	err = (*(vp)->v_op->vop_pathconf)(vp, cmd, valp, cr);
+	VOPSTATS_UPDATE(vp, npathconf);
+	return (err);
 }
 
 int
@@ -2842,7 +3367,11 @@
 	int flags,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_pageio)(vp, pp, io_off, io_len, flags, cr);
+	int	err;
+
+	err = (*(vp)->v_op->vop_pageio)(vp, pp, io_off, io_len, flags, cr);
+	VOPSTATS_UPDATE(vp, npageio);
+	return (err);
 }
 
 int
@@ -2851,7 +3380,10 @@
 	int action,
 	int *blkp)
 {
-	return (*(vp)->v_op->vop_dumpctl)(vp, action, blkp);
+	int	err;
+	err = (*(vp)->v_op->vop_dumpctl)(vp, action, blkp);
+	VOPSTATS_UPDATE(vp, ndumpctl);
+	return (err);
 }
 
 void
@@ -2862,6 +3394,8 @@
 	int dn,
 	cred_t *cr)
 {
+	/* Must do stats first since it's possible to lose the vnode */
+	VOPSTATS_UPDATE(vp, ndispose);
 	(*(vp)->v_op->vop_dispose)(vp, pp, flag, dn, cr);
 }
 
@@ -2872,7 +3406,11 @@
 	int flag,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_setsecattr) (vp, vsap, flag, cr);
+	int	err;
+
+	err = (*(vp)->v_op->vop_setsecattr) (vp, vsap, flag, cr);
+	VOPSTATS_UPDATE(vp, nsetsecattr);
+	return (err);
 }
 
 int
@@ -2882,7 +3420,11 @@
 	int flag,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_getsecattr) (vp, vsap, flag, cr);
+	int	err;
+
+	err = (*(vp)->v_op->vop_getsecattr) (vp, vsap, flag, cr);
+	VOPSTATS_UPDATE(vp, ngetsecattr);
+	return (err);
 }
 
 int
@@ -2893,11 +3435,19 @@
 	int flag,
 	cred_t *cr)
 {
-	return (*(vp)->v_op->vop_shrlock)(vp, cmd, shr, flag, cr);
+	int	err;
+
+	err = (*(vp)->v_op->vop_shrlock)(vp, cmd, shr, flag, cr);
+	VOPSTATS_UPDATE(vp, nshrlock);
+	return (err);
 }
 
 int
 fop_vnevent(vnode_t *vp, vnevent_t vnevent)
 {
-	return (*(vp)->v_op->vop_vnevent)(vp, vnevent);
+	int	err;
+
+	err = (*(vp)->v_op->vop_vnevent)(vp, vnevent);
+	VOPSTATS_UPDATE(vp, nvnevent);
+	return (err);
 }
--- a/usr/src/uts/common/fs/zfs/zfs_vfsops.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/fs/zfs/zfs_vfsops.c	Sat Feb 25 01:33:06 2006 -0800
@@ -1072,7 +1072,7 @@
 	VFSDEF_VERSION,
 	MNTTYPE_ZFS,
 	zfs_vfsinit,
-	VSW_HASPROTO | VSW_CANRWRO | VSW_CANREMOUNT | VSW_VOLATILEDEV,
+	VSW_HASPROTO|VSW_CANRWRO|VSW_CANREMOUNT|VSW_VOLATILEDEV|VSW_STATS,
 	&zfs_mntopts
 };
 
--- a/usr/src/uts/common/os/modconf.c	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/os/modconf.c	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -34,6 +33,7 @@
 #include <sys/conf.h>
 #include <sys/class.h>
 #include <sys/vfs.h>
+#include <sys/vnode.h>
 #include <sys/mount.h>
 #include <sys/systm.h>
 #include <sys/modctl.h>
@@ -56,6 +56,7 @@
 #include <sys/strsubr.h>
 #include <sys/kcpc.h>
 #include <sys/cpc_pcbe.h>
+#include <sys/kstat.h>
 
 extern int moddebug;
 
@@ -890,8 +891,16 @@
 	struct vfssw *vswp;
 	struct modctl *mcp;
 	char *fsname;
+	char ksname[KSTAT_STRLEN + 1];
+	int fstype;	/* index into vfssw[] and vsanchor_fstype[] */
 	int allocated;
 	int err;
+	int vsw_stats_enabled;
+	/* Not for public consumption so these aren't in a header file */
+	extern int	vopstats_enabled;
+	extern vopstats_t **vopstats_fstype;
+	extern kstat_t *new_vskstat(char *, vopstats_t *);
+	extern void initialize_vopstats(vopstats_t *);
 
 	if (modl->fs_vfsdef->def_version == VFSDEF_VERSION) {
 		/* Version matched */
@@ -931,7 +940,11 @@
 	}
 	ASSERT(vswp != NULL);
 
-	vswp->vsw_flag = modl->fs_vfsdef->flags;
+	fstype = vswp - vfssw;	/* Pointer arithmetic to get the fstype */
+
+	/* Turn on everything by default *except* VSW_STATS */
+	vswp->vsw_flag = modl->fs_vfsdef->flags & ~(VSW_STATS);
+
 	if (modl->fs_vfsdef->flags & VSW_HASPROTO) {
 		vfs_mergeopttbl(&vfs_mntopts, modl->fs_vfsdef->optproto,
 		    &vswp->vsw_optproto);
@@ -945,10 +958,19 @@
 		 */
 		vswp->vsw_flag |= VSW_CANREMOUNT;
 	}
+
+	/*
+	 * If stats are enabled system wide and for this fstype, then
+	 * set the VSW_STATS flag in the proper vfssw[] table entry.
+	 */
+	if (vopstats_enabled && modl->fs_vfsdef->flags & VSW_STATS) {
+		vswp->vsw_flag |= VSW_STATS;
+	}
+
 	if (modl->fs_vfsdef->init == NULL)
 		err = EFAULT;
 	else
-		err = (*(modl->fs_vfsdef->init))(vswp - vfssw, fsname);
+		err = (*(modl->fs_vfsdef->init))(fstype, fsname);
 
 	if (err != 0) {
 		if (allocated) {
@@ -959,9 +981,22 @@
 		vswp->vsw_init = NULL;
 	}
 
+	/* We don't want to hold the vfssw[] write lock over a kmem_alloc() */
+	vsw_stats_enabled = vswp->vsw_flag & VSW_STATS;
+
 	vfs_unrefvfssw(vswp);
 	WUNLOCK_VFSSW();
 
+	/* If everything is on, set up the per-fstype vopstats */
+	if (vsw_stats_enabled && vopstats_enabled &&
+	    vopstats_fstype && vopstats_fstype[fstype] == NULL) {
+		(void) strlcpy(ksname, VOPSTATS_STR, sizeof (ksname));
+		(void) strlcat(ksname, vfssw[fstype].vsw_name, sizeof (ksname));
+		vopstats_fstype[fstype] =
+		    kmem_alloc(sizeof (vopstats_t), KM_SLEEP);
+		initialize_vopstats(vopstats_fstype[fstype]);
+		(void) new_vskstat(ksname, vopstats_fstype[fstype]);
+	}
 	return (err);
 }
 
--- a/usr/src/uts/common/sys/vfs.h	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/sys/vfs.h	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -157,6 +156,24 @@
 } mntopts_t;
 
 /*
+ * The kstat structures associated with the vopstats are kept in an
+ * AVL tree.  This is to avoid the case where a file system does not
+ * use a unique fsid_t for each vfs (e.g., namefs).  In order to do
+ * this, we need a structure that the AVL tree can use that also
+ * references the kstat.
+ * Note that the vks_fsid is generated from the value reported by
+ * VFS_STATVFS().
+ */
+typedef struct vskstat_anchor {
+	avl_node_t	vsk_node;	/* Required for use by AVL routines */
+	kstat_t		*vsk_ksp;	/* kstat structure for vopstats */
+	ulong_t		vsk_fsid;	/* fsid associated w/this FS */
+} vsk_anchor_t;
+
+extern avl_tree_t	vskstat_tree;
+extern kmutex_t		vskstat_tree_lock;
+
+/*
  * Structure per mounted file system.  Each mounted file system has
  * an array of operations and an instance record.
  *
@@ -215,6 +232,12 @@
 	struct zone	*vfs_zone;		/* zone that owns the mount */
 	struct vfs	*vfs_zone_next;		/* next VFS visible in zone */
 	struct vfs	*vfs_zone_prev;		/* prev VFS visible in zone */
+	/*
+	 * Support for statistics on the vnode operations
+	 */
+	vsk_anchor_t	*vfs_vskap;		/* anchor for vopstats' kstat */
+	vopstats_t	*vfs_fstypevsp;		/* ptr to per-fstype vopstats */
+	vopstats_t	vfs_vopstats;		/* per-mount vnode op stats */
 } vfs_t;
 
 /*
@@ -232,6 +255,7 @@
 #define	VFS_XATTR	0x400		/* fs supports extended attributes */
 #define	VFS_NODEVICES	0x800		/* device-special files disallowed */
 #define	VFS_NOEXEC	0x1000		/* executables disallowed */
+#define	VFS_STATS	0x2000		/* file system can collect stats */
 
 #define	VFS_NORESOURCE	"unspecified_resource"
 #define	VFS_NOMNTPT	"unspecified_mountpoint"
@@ -274,8 +298,6 @@
 typedef enum vntrans vntrans_t;
 
 
-
-
 /*
  * Operations supported on virtual file system.
  */
@@ -374,6 +396,7 @@
 #define	VSW_CANREMOUNT	0x04	/* file system supports remounts */
 #define	VSW_NOTZONESAFE	0x08	/* zone_enter(2) should fail for these files */
 #define	VSW_VOLATILEDEV	0x10	/* vfs_dev can change each time fs is mounted */
+#define	VSW_STATS	0x20	/* file system can collect stats */
 
 #define	VSW_INSTALLED	0x8000	/* this vsw is associated with a file system */
 
--- a/usr/src/uts/common/sys/vnode.h	Sat Feb 25 00:24:50 2006 -0800
+++ b/usr/src/uts/common/sys/vnode.h	Sat Feb 25 01:33:06 2006 -0800
@@ -2,9 +2,8 @@
  * 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.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
  *
  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  * or http://www.opensolaris.org/os/licensing.
@@ -20,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -50,6 +49,9 @@
 #include <sys/uio.h>
 #include <sys/resource.h>
 #include <vm/seg_enum.h>
+#include <sys/kstat.h>
+#include <sys/kmem.h>
+#include <sys/avl.h>
 #ifdef	_KERNEL
 #include <sys/buf.h>
 #endif	/* _KERNEL */
@@ -94,6 +96,68 @@
 
 
 /*
+ * Statistics for all vnode operations.
+ * All operations record number of ops (since boot/mount/zero'ed).
+ * Certain I/O operations (read, write, readdir) also record number
+ * of bytes transferred.
+ * This appears in two places in the system: one is embedded in each
+ * vfs_t.  There is also an array of vopstats_t structures allocated
+ * on a per-fstype basis.
+ */
+
+#define	VOPSTATS_STR	"vopstats_"	/* Initial string for vopstat kstats */
+
+typedef struct vopstats {
+	kstat_named_t	nopen;		/* VOP_OPEN */
+	kstat_named_t	nclose;		/* VOP_CLOSE */
+	kstat_named_t	nread;		/* VOP_READ */
+	kstat_named_t	read_bytes;
+	kstat_named_t	nwrite;		/* VOP_WRITE */
+	kstat_named_t	write_bytes;
+	kstat_named_t	nioctl;		/* VOP_IOCTL */
+	kstat_named_t	nsetfl;		/* VOP_SETFL */
+	kstat_named_t	ngetattr;	/* VOP_GETATTR */
+	kstat_named_t	nsetattr;	/* VOP_SETATTR */
+	kstat_named_t	naccess;	/* VOP_ACCESS */
+	kstat_named_t	nlookup;	/* VOP_LOOKUP */
+	kstat_named_t	ncreate;	/* VOP_CREATE */
+	kstat_named_t	nremove;	/* VOP_REMOVE */
+	kstat_named_t	nlink;		/* VOP_LINK */
+	kstat_named_t	nrename;	/* VOP_RENAME */
+	kstat_named_t	nmkdir;		/* VOP_MKDIR */
+	kstat_named_t	nrmdir;		/* VOP_RMDIR */
+	kstat_named_t	nreaddir;	/* VOP_READDIR */
+	kstat_named_t	readdir_bytes;
+	kstat_named_t	nsymlink;	/* VOP_SYMLINK */
+	kstat_named_t	nreadlink;	/* VOP_READLINK */
+	kstat_named_t	nfsync;		/* VOP_FSYNC */
+	kstat_named_t	ninactive;	/* VOP_INACTIVE */
+	kstat_named_t	nfid;		/* VOP_FID */
+	kstat_named_t	nrwlock;	/* VOP_RWLOCK */
+	kstat_named_t	nrwunlock;	/* VOP_RWUNLOCK */
+	kstat_named_t	nseek;		/* VOP_SEEK */
+	kstat_named_t	ncmp;		/* VOP_CMP */
+	kstat_named_t	nfrlock;	/* VOP_FRLOCK */
+	kstat_named_t	nspace;		/* VOP_SPACE */
+	kstat_named_t	nrealvp;	/* VOP_REALVP */
+	kstat_named_t	ngetpage;	/* VOP_GETPAGE */
+	kstat_named_t	nputpage;	/* VOP_PUTPAGE */
+	kstat_named_t	nmap;		/* VOP_MAP */
+	kstat_named_t	naddmap;	/* VOP_ADDMAP */
+	kstat_named_t	ndelmap;	/* VOP_DELMAP */
+	kstat_named_t	npoll;		/* VOP_POLL */
+	kstat_named_t	ndump;		/* VOP_DUMP */
+	kstat_named_t	npathconf;	/* VOP_PATHCONF */
+	kstat_named_t	npageio;	/* VOP_PAGEIO */
+	kstat_named_t	ndumpctl;	/* VOP_DUMPCTL */
+	kstat_named_t	ndispose;	/* VOP_DISPOSE */
+	kstat_named_t	nsetsecattr;	/* VOP_SETSECATTR */
+	kstat_named_t	ngetsecattr;	/* VOP_GETSECATTR */
+	kstat_named_t	nshrlock;	/* VOP_SHRLOCK */
+	kstat_named_t	nvnevent;	/* VOP_VNEVENT */
+} vopstats_t;
+
+/*
  * The vnode is the focus of all file activity in UNIX.
  * A vnode is allocated for each active file, each current
  * directory, each mounted-on file, and the root.