Print this page
NEX-16731 Panic in exp_kstats_reset() if exp_kstats is NULL
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
NEX-16917 Need to reduce the impact of NFS per-share kstats on failover
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
NEX-6778 NFS kstats leak and cause system to hang
Revert "NEX-4261 Per-client NFS server IOPS, bandwidth, and latency kstats"
This reverts commit 586c3ab1927647487f01c337ddc011c642575a52.
Revert "NEX-5354 Aggregated IOPS, bandwidth, and latency kstats for NFS server"
This reverts commit c91d7614da8618ef48018102b077f60ecbbac8c2.
Revert "NEX-5667 nfssrv_stats_flags does not work for aggregated kstats"
This reverts commit 3dcf42618be7dd5f408c327f429c81e07ca08e74.
Revert "NEX-5750 Time values for aggregated NFS server kstats should be normalized"
This reverts commit 1f4d4f901153b0191027969fa4a8064f9d3b9ee1.
Revert "NEX-5942 Panic in rfs4_minorvers_mismatch() with NFSv4.1 client"
This reverts commit 40766417094a162f5e4cc8786c0fa0a7e5871cd9.
Revert "NEX-5752 NFS server: namespace collision in kstats"
This reverts commit ae81e668db86050da8e483264acb0cce0444a132.
Reviewed by: Rob Gittins <rob.gittins@nexenta.com>
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
NEX-4261 Per-client NFS server IOPS, bandwidth, and latency kstats
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-3097 IOPS, bandwidth, and latency kstats for NFS server
Reviewed by: Josef 'Jeff' Sipek <josef.sipek@nexenta.com>
        
@@ -16,11 +16,16 @@
  * fields enclosed by brackets "[]" replaced with your own identifying
  * information: Portions Copyright [yyyy] [name of copyright owner]
  *
  * CDDL HEADER END
  */
+
 /*
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+/*
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
 #include <sys/types.h>
@@ -43,12 +48,11 @@
  * module:0:name:"misc", using the provided template to initialize the names
  * and values of the stats.
  */
 static kstat_named_t *
 nfsstat_zone_init_common(zoneid_t zoneid, const char *module, int vers,
-                            const char *name, const kstat_named_t *template,
-                            size_t template_size)
+    const char *name, const kstat_named_t *template, size_t template_size)
 {
         kstat_t *ksp;
         kstat_named_t *ks_data;
 
         ks_data = kmem_alloc(template_size, KM_SLEEP);
@@ -125,10 +129,57 @@
                 kmem_free(svstatp[vers], sizeof (svstat_tmpl));
         }
 }
 
 /*
+ * Support functions for the kstat_io alloc/free
+ */
+static kstat_t **
+rfs_kstat_io_init(zoneid_t zoneid, const char *module, int instance,
+    const char *name, const char *class, const kstat_named_t *tmpl, int count,
+    kmutex_t *lock)
+{
+        int i;
+        kstat_t **ret = kmem_alloc(count * sizeof (*ret), KM_SLEEP);
+
+        for (i = 0; i < count; i++) {
+                char namebuf[KSTAT_STRLEN];
+
+                (void) snprintf(namebuf, sizeof (namebuf), "%s_%s", name,
+                    tmpl[i].name);
+                ret[i] = kstat_create_zone(module, instance, namebuf, class,
+                    KSTAT_TYPE_IO, 1, 0, zoneid);
+                if (ret[i] != NULL) {
+                        ret[i]->ks_lock = lock;
+                        kstat_install(ret[i]);
+                }
+        }
+
+        return (ret);
+}
+
+static void
+rfs_kstat_io_delete(kstat_t **ks, int count)
+{
+        int i;
+
+        for (i = 0; i < count; i++) {
+                if (ks[i] != NULL) {
+                        kstat_delete(ks[i]);
+                        ks[i] = NULL;
+                }
+        }
+}
+
+static void
+rfs_kstat_io_free(kstat_t **ks, int count)
+{
+        rfs_kstat_io_delete(ks, count);
+        kmem_free(ks, count * sizeof (*ks));
+}
+
+/*
  * NFSv2 client stats
  */
 static const kstat_named_t rfsreqcnt_v2_tmpl[] = {
         { "null",       KSTAT_DATA_UINT64 },
         { "getattr",    KSTAT_DATA_UINT64 },
@@ -186,31 +237,48 @@
         { "rmdir",      KSTAT_DATA_UINT64 },
         { "readdir",    KSTAT_DATA_UINT64 },
         { "statfs",     KSTAT_DATA_UINT64 }
 };
 
+#define RFSPROCCNT_V2_COUNT     \
+        (sizeof (rfsproccnt_v2_tmpl) / sizeof (rfsproccnt_v2_tmpl[0]))
+
 kstat_named_t *rfsproccnt_v2_ptr;
+kstat_t **rfsprocio_v2_ptr;
 
 static void
 nfsstat_zone_init_rfsproc_v2(zoneid_t zoneid, struct nfs_version_stats *statsp)
 {
-        kstat_named_t *ks_data;
+        statsp->rfsproccnt_ptr = nfsstat_zone_init_common(zoneid, "nfs", 0,
+            "rfsproccnt_v2", rfsproccnt_v2_tmpl, sizeof (rfsproccnt_v2_tmpl));
 
-        ks_data = nfsstat_zone_init_common(zoneid, "nfs", 0, "rfsproccnt_v2",
-            rfsproccnt_v2_tmpl, sizeof (rfsproccnt_v2_tmpl));
-        statsp->rfsproccnt_ptr = ks_data;
-        if (zoneid == GLOBAL_ZONEID)
-                rfsproccnt_v2_ptr = ks_data;
+        mutex_init(&statsp->rfsprocio_lock, NULL, MUTEX_DEFAULT, NULL);
+
+        statsp->rfsprocio_ptr = rfs_kstat_io_init(zoneid, "nfs", 0,
+            "rfsprocio_v2", "rfsprocio_v2", rfsproccnt_v2_tmpl,
+            RFSPROCCNT_V2_COUNT, &statsp->rfsprocio_lock);
+
+        if (zoneid == GLOBAL_ZONEID) {
+                rfsproccnt_v2_ptr = statsp->rfsproccnt_ptr;
+                rfsprocio_v2_ptr = statsp->rfsprocio_ptr;
+        }
 }
 
 static void
 nfsstat_zone_fini_rfsproc_v2(zoneid_t zoneid, struct nfs_version_stats *statsp)
 {
-        if (zoneid == GLOBAL_ZONEID)
+        if (zoneid == GLOBAL_ZONEID) {
                 rfsproccnt_v2_ptr = NULL;
+                rfsprocio_v2_ptr = NULL;
+        }
+
         nfsstat_zone_fini_common(zoneid, "nfs", 0, "rfsproccnt_v2");
         kmem_free(statsp->rfsproccnt_ptr, sizeof (rfsproccnt_v2_tmpl));
+
+        rfs_kstat_io_free(statsp->rfsprocio_ptr, RFSPROCCNT_V2_COUNT);
+
+        mutex_destroy(&statsp->rfsprocio_lock);
 }
 
 /*
  * NFSv2 client ACL stats
  */
@@ -247,32 +315,48 @@
         { "getattr",    KSTAT_DATA_UINT64 },
         { "access",     KSTAT_DATA_UINT64 },
         { "getxattrdir",        KSTAT_DATA_UINT64 }
 };
 
+#define ACLPROCCNT_V2_COUNT     \
+        (sizeof (aclproccnt_v2_tmpl) / sizeof (aclproccnt_v2_tmpl[0]))
+
 kstat_named_t *aclproccnt_v2_ptr;
+kstat_t **aclprocio_v2_ptr;
 
 static void
 nfsstat_zone_init_aclproc_v2(zoneid_t zoneid, struct nfs_version_stats *statsp)
 {
-        kstat_named_t *ks_data;
+        statsp->aclproccnt_ptr = nfsstat_zone_init_common(zoneid, "nfs_acl", 0,
+            "aclproccnt_v2", aclproccnt_v2_tmpl, sizeof (aclproccnt_v2_tmpl));
 
-        ks_data = nfsstat_zone_init_common(zoneid, "nfs_acl", 0,
-            "aclproccnt_v2", aclproccnt_v2_tmpl,
-            sizeof (aclproccnt_v2_tmpl));
-        statsp->aclproccnt_ptr = ks_data;
-        if (zoneid == GLOBAL_ZONEID)
-                aclproccnt_v2_ptr = ks_data;
+        mutex_init(&statsp->aclprocio_lock, NULL, MUTEX_DEFAULT, NULL);
+
+        statsp->aclprocio_ptr = rfs_kstat_io_init(zoneid, "nfs_acl", 0,
+            "aclprocio_v2", "aclprocio_v2", aclproccnt_v2_tmpl,
+            ACLPROCCNT_V2_COUNT, &statsp->aclprocio_lock);
+
+        if (zoneid == GLOBAL_ZONEID) {
+                aclproccnt_v2_ptr = statsp->aclproccnt_ptr;
+                aclprocio_v2_ptr = statsp->aclprocio_ptr;
+        }
 }
 
 static void
 nfsstat_zone_fini_aclproc_v2(zoneid_t zoneid, struct nfs_version_stats *statsp)
 {
-        if (zoneid == GLOBAL_ZONEID)
+        if (zoneid == GLOBAL_ZONEID) {
                 aclproccnt_v2_ptr = NULL;
+                aclprocio_v2_ptr = NULL;
+        }
+
         nfsstat_zone_fini_common(zoneid, "nfs_acl", 0, "aclproccnt_v2");
         kmem_free(statsp->aclproccnt_ptr, sizeof (aclproccnt_v2_tmpl));
+
+        rfs_kstat_io_free(statsp->aclprocio_ptr, ACLPROCCNT_V2_COUNT);
+
+        mutex_destroy(&statsp->aclprocio_lock);
 }
 
 /*
  * NFSv3 client stats
  */
@@ -341,31 +425,48 @@
         { "fsinfo",     KSTAT_DATA_UINT64 },
         { "pathconf",   KSTAT_DATA_UINT64 },
         { "commit",     KSTAT_DATA_UINT64 }
 };
 
+#define RFSPROCCNT_V3_COUNT     \
+        (sizeof (rfsproccnt_v3_tmpl) / sizeof (rfsproccnt_v3_tmpl[0]))
+
 kstat_named_t *rfsproccnt_v3_ptr;
+kstat_t **rfsprocio_v3_ptr;
 
 static void
 nfsstat_zone_init_rfsproc_v3(zoneid_t zoneid, struct nfs_version_stats *statsp)
 {
-        kstat_named_t *ks_data;
+        statsp->rfsproccnt_ptr = nfsstat_zone_init_common(zoneid, "nfs", 0,
+            "rfsproccnt_v3", rfsproccnt_v3_tmpl, sizeof (rfsproccnt_v3_tmpl));
 
-        ks_data = nfsstat_zone_init_common(zoneid, "nfs", 0, "rfsproccnt_v3",
-            rfsproccnt_v3_tmpl, sizeof (rfsproccnt_v3_tmpl));
-        statsp->rfsproccnt_ptr = ks_data;
-        if (zoneid == GLOBAL_ZONEID)
-                rfsproccnt_v3_ptr = ks_data;
+        mutex_init(&statsp->rfsprocio_lock, NULL, MUTEX_DEFAULT, NULL);
+
+        statsp->rfsprocio_ptr = rfs_kstat_io_init(zoneid, "nfs", 0,
+            "rfsprocio_v3", "rfsprocio_v3", rfsproccnt_v3_tmpl,
+            RFSPROCCNT_V3_COUNT, &statsp->rfsprocio_lock);
+
+        if (zoneid == GLOBAL_ZONEID) {
+                rfsproccnt_v3_ptr = statsp->rfsproccnt_ptr;
+                rfsprocio_v3_ptr = statsp->rfsprocio_ptr;
+        }
 }
 
 static void
 nfsstat_zone_fini_rfsproc_v3(zoneid_t zoneid, struct nfs_version_stats *statsp)
 {
-        if (zoneid == GLOBAL_ZONEID)
+        if (zoneid == GLOBAL_ZONEID) {
                 rfsproccnt_v3_ptr = NULL;
+                rfsprocio_v3_ptr = NULL;
+        }
+
         nfsstat_zone_fini_common(zoneid, "nfs", 0, "rfsproccnt_v3");
         kmem_free(statsp->rfsproccnt_ptr, sizeof (rfsproccnt_v3_tmpl));
+
+        rfs_kstat_io_free(statsp->rfsprocio_ptr, RFSPROCCNT_V3_COUNT);
+
+        mutex_destroy(&statsp->rfsprocio_lock);
 }
 
 /*
  * NFSv3 client ACL stats
  */
@@ -398,32 +499,48 @@
         { "getacl",     KSTAT_DATA_UINT64 },
         { "setacl",     KSTAT_DATA_UINT64 },
         { "getxattrdir",        KSTAT_DATA_UINT64 }
 };
 
+#define ACLPROCCNT_V3_COUNT     \
+        (sizeof (aclproccnt_v3_tmpl) / sizeof (aclproccnt_v3_tmpl[0]))
+
 kstat_named_t *aclproccnt_v3_ptr;
+kstat_t **aclprocio_v3_ptr;
 
 static void
 nfsstat_zone_init_aclproc_v3(zoneid_t zoneid, struct nfs_version_stats *statsp)
 {
-        kstat_named_t *ks_data;
+        statsp->aclproccnt_ptr = nfsstat_zone_init_common(zoneid, "nfs_acl", 0,
+            "aclproccnt_v3", aclproccnt_v3_tmpl, sizeof (aclproccnt_v3_tmpl));
 
-        ks_data = nfsstat_zone_init_common(zoneid, "nfs_acl", 0,
-            "aclproccnt_v3", aclproccnt_v3_tmpl,
-            sizeof (aclproccnt_v3_tmpl));
-        statsp->aclproccnt_ptr = ks_data;
-        if (zoneid == GLOBAL_ZONEID)
-                aclproccnt_v3_ptr = ks_data;
+        mutex_init(&statsp->aclprocio_lock, NULL, MUTEX_DEFAULT, NULL);
+
+        statsp->aclprocio_ptr = rfs_kstat_io_init(zoneid, "nfs_acl", 0,
+            "aclprocio_v3", "aclprocio_v3", aclproccnt_v3_tmpl,
+            ACLPROCCNT_V3_COUNT, &statsp->aclprocio_lock);
+
+        if (zoneid == GLOBAL_ZONEID) {
+                aclproccnt_v3_ptr = statsp->aclproccnt_ptr;
+                aclprocio_v3_ptr = statsp->aclprocio_ptr;
+        }
 }
 
 static void
 nfsstat_zone_fini_aclproc_v3(zoneid_t zoneid, struct nfs_version_stats *statsp)
 {
-        if (zoneid == GLOBAL_ZONEID)
+        if (zoneid == GLOBAL_ZONEID) {
                 aclproccnt_v3_ptr = NULL;
+                aclprocio_v3_ptr = NULL;
+        }
+
         nfsstat_zone_fini_common(zoneid, "nfs_acl", 0, "aclproccnt_v3");
         kmem_free(statsp->aclproccnt_ptr, sizeof (aclproccnt_v3_tmpl));
+
+        rfs_kstat_io_free(statsp->aclprocio_ptr, ACLPROCCNT_V3_COUNT);
+
+        mutex_destroy(&statsp->aclprocio_lock);
 }
 
 /*
  * NFSv4 client stats
  */
@@ -528,31 +645,48 @@
         { "write",      KSTAT_DATA_UINT64 },
         { "release_lockowner",  KSTAT_DATA_UINT64 },
         { "illegal",    KSTAT_DATA_UINT64 },
 };
 
+#define RFSPROCCNT_V4_COUNT     \
+        (sizeof (rfsproccnt_v4_tmpl) / sizeof (rfsproccnt_v4_tmpl[0]))
+
 kstat_named_t *rfsproccnt_v4_ptr;
+kstat_t **rfsprocio_v4_ptr;
 
 static void
 nfsstat_zone_init_rfsproc_v4(zoneid_t zoneid, struct nfs_version_stats *statsp)
 {
-        kstat_named_t *ks_data;
+        statsp->rfsproccnt_ptr = nfsstat_zone_init_common(zoneid, "nfs", 0,
+            "rfsproccnt_v4", rfsproccnt_v4_tmpl, sizeof (rfsproccnt_v4_tmpl));
 
-        ks_data = nfsstat_zone_init_common(zoneid, "nfs", 0, "rfsproccnt_v4",
-            rfsproccnt_v4_tmpl, sizeof (rfsproccnt_v4_tmpl));
-        statsp->rfsproccnt_ptr = ks_data;
-        if (zoneid == GLOBAL_ZONEID)
-                rfsproccnt_v4_ptr = ks_data;
+        mutex_init(&statsp->rfsprocio_lock, NULL, MUTEX_DEFAULT, NULL);
+
+        statsp->rfsprocio_ptr = rfs_kstat_io_init(zoneid, "nfs", 0,
+            "rfsprocio_v4", "rfsprocio_v4", rfsproccnt_v4_tmpl,
+            RFSPROCCNT_V4_COUNT, &statsp->rfsprocio_lock);
+
+        if (zoneid == GLOBAL_ZONEID) {
+                rfsproccnt_v4_ptr = statsp->rfsproccnt_ptr;
+                rfsprocio_v4_ptr = statsp->rfsprocio_ptr;
+        }
 }
 
 static void
 nfsstat_zone_fini_rfsproc_v4(zoneid_t zoneid, struct nfs_version_stats *statsp)
 {
-        if (zoneid == GLOBAL_ZONEID)
+        if (zoneid == GLOBAL_ZONEID) {
                 rfsproccnt_v4_ptr = NULL;
+                rfsprocio_v4_ptr = NULL;
+        }
+
         nfsstat_zone_fini_common(zoneid, "nfs", 0, "rfsproccnt_v4");
         kmem_free(statsp->rfsproccnt_ptr, sizeof (rfsproccnt_v4_tmpl));
+
+        rfs_kstat_io_free(statsp->rfsprocio_ptr, RFSPROCCNT_V4_COUNT);
+
+        mutex_destroy(&statsp->rfsprocio_lock);
 }
 
 /*
  * NFSv4 client ACL stats
  */
@@ -607,11 +741,26 @@
                 aclproccnt_v4_ptr = NULL;
         nfsstat_zone_fini_common(zoneid, "nfs_acl", 0, "aclproccnt_v4");
         kmem_free(statsp->aclproccnt_ptr, sizeof (aclproccnt_v4_tmpl));
 }
 
+
 /*
+ * NFS server per share kstats (exp_kstats)
+ * kstats are collected per share for NFSv3 & NFSv4 read and write operations.
+ */
+#define NFSSRV_SHR_READ         0
+#define NFSSRV_SHR_WRITE        1
+
+static const kstat_named_t rfsshr_tmpl[] = {
+        { "read",       KSTAT_DATA_UINT64 },    /* NFSSRV_SHR_READ */
+        { "write",      KSTAT_DATA_UINT64 }     /* NFSSRV_SHR_WRITE */
+};
+#define RFSSHARE_COUNT  \
+        (sizeof (rfsshr_tmpl) / sizeof (rfsshr_tmpl[0]))
+
+/*
  * Zone initializer callback to setup the kstats.
  */
 void *
 nfsstat_zone_init(zoneid_t zoneid)
 {
@@ -683,6 +832,169 @@
         nfsstat_zone_fini_rfsproc_v4(zoneid, &nfs_stats_ptr->nfs_stats_v4);
         nfsstat_zone_fini_aclreq_v4(zoneid, &nfs_stats_ptr->nfs_stats_v4);
         nfsstat_zone_fini_aclproc_v4(zoneid, &nfs_stats_ptr->nfs_stats_v4);
 
         kmem_free(nfs_stats_ptr, sizeof (*nfs_stats_ptr));
+}
+
+/*
+ * Support for exp_kstats initialization and tear down
+ */
+struct exp_kstats *
+exp_kstats_init(zoneid_t zoneid, int instance, const char *path, size_t len,
+    bool_t pseudo)
+{
+        struct exp_kstats *exp_kstats;
+
+        exp_kstats = kmem_alloc(sizeof (*exp_kstats), KM_SLEEP);
+
+        mutex_init(&exp_kstats->procio_lock, NULL, MUTEX_DEFAULT, NULL);
+
+        /*
+         * Generic share kstat.
+         */
+        exp_kstats->share_kstat = kstat_create_zone("nfs", instance, "share",
+            "misc", KSTAT_TYPE_NAMED,
+            sizeof (exp_kstats->share_kstat_data) / sizeof (kstat_named_t),
+            KSTAT_FLAG_VIRTUAL | KSTAT_FLAG_VAR_SIZE, zoneid);
+        if (exp_kstats->share_kstat != NULL) {
+                len = strnlen(path, len);
+                exp_kstats->share_path = kmem_alloc(len + 1, KM_SLEEP);
+                bcopy(path, exp_kstats->share_path, len);
+                exp_kstats->share_path[len] = '\0';
+
+                exp_kstats->share_kstat->ks_data =
+                    &exp_kstats->share_kstat_data;
+
+                kstat_named_init(&exp_kstats->share_kstat_data.path, "path",
+                    KSTAT_DATA_STRING);
+                kstat_named_setstr(&exp_kstats->share_kstat_data.path,
+                    exp_kstats->share_path);
+
+                kstat_named_init(&exp_kstats->share_kstat_data.filesystem,
+                    "filesystem", KSTAT_DATA_STRING);
+                kstat_named_setstr(&exp_kstats->share_kstat_data.filesystem,
+                    pseudo ? "pseudo" : "real");
+
+                exp_kstats->share_kstat->ks_lock = &exp_kstats->procio_lock;
+                kstat_install(exp_kstats->share_kstat);
+        }
+
+        /* NFS version 3 */
+        exp_kstats->rfsshr_v3_ptr = rfs_kstat_io_init(zoneid, "nfs",
+            instance, "share_v3", "rfsprocio_v3", rfsshr_tmpl,
+            RFSSHARE_COUNT, &exp_kstats->procio_lock);
+
+        /* NFS version 4 */
+        exp_kstats->rfsshr_v4_ptr = rfs_kstat_io_init(zoneid, "nfs",
+            instance, "share_v4", "rfsprocio_v4", rfsshr_tmpl,
+            RFSSHARE_COUNT, &exp_kstats->procio_lock);
+
+        return (exp_kstats);
+}
+
+void
+exp_kstats_delete(struct exp_kstats *exp_kstats)
+{
+        if (exp_kstats == NULL)
+                return;
+
+        /* Generic share kstat */
+        if (exp_kstats->share_kstat != NULL) {
+                kstat_delete(exp_kstats->share_kstat);
+                exp_kstats->share_kstat = NULL;
+                strfree(exp_kstats->share_path);
+        }
+
+        rfs_kstat_io_delete(exp_kstats->rfsshr_v3_ptr, RFSSHARE_COUNT);
+        rfs_kstat_io_delete(exp_kstats->rfsshr_v4_ptr, RFSSHARE_COUNT);
+
+}
+
+void
+exp_kstats_fini(struct exp_kstats *exp_kstats)
+{
+        if (exp_kstats == NULL)
+                return;
+
+        /* Generic share kstat */
+        if (exp_kstats->share_kstat != NULL) {
+                kstat_delete(exp_kstats->share_kstat);
+                strfree(exp_kstats->share_path);
+        }
+
+        rfs_kstat_io_free(exp_kstats->rfsshr_v3_ptr, RFSSHARE_COUNT);
+        rfs_kstat_io_free(exp_kstats->rfsshr_v4_ptr, RFSSHARE_COUNT);
+
+        mutex_destroy(&exp_kstats->procio_lock);
+
+        kmem_free(exp_kstats, sizeof (*exp_kstats));
+}
+
+void
+exp_kstats_reset(struct exp_kstats *exp_kstats, const char *path, size_t len,
+    bool_t pseudo)
+{
+        char *old;
+        char *new;
+
+        if ((exp_kstats == NULL) || (exp_kstats->share_kstat == NULL))
+                return;
+
+        len = strnlen(path, len);
+        new = kmem_alloc(len + 1, KM_SLEEP);
+        bcopy(path, new, len);
+        new[len] = '\0';
+
+        mutex_enter(exp_kstats->share_kstat->ks_lock);
+        old = exp_kstats->share_path;
+        exp_kstats->share_path = new;
+        kstat_named_setstr(&exp_kstats->share_kstat_data.path,
+            exp_kstats->share_path);
+        kstat_named_setstr(&exp_kstats->share_kstat_data.filesystem,
+            pseudo ? "pseudo" : "real");
+        mutex_exit(exp_kstats->share_kstat->ks_lock);
+
+        strfree(old);
+}
+
+kstat_t *
+/* LINTED E_FUNC_ARG_UNUSED */
+exp_kstats_v2(struct exp_kstats *exp_kstats, uint_t op)
+{
+        /* No NFS v2 per-share kstats */
+        return (NULL);
+}
+
+kstat_t *
+exp_kstats_v3(struct exp_kstats *exp_kstats, uint_t op)
+{
+        if (exp_kstats == NULL)
+                return (NULL);
+
+        /* per share kstats for selected operations (read, write) only */
+        switch (op) {
+        case NFSPROC3_READ:
+                return (exp_kstats->rfsshr_v3_ptr[NFSSRV_SHR_READ]);
+        case NFSPROC3_WRITE:
+                return (exp_kstats->rfsshr_v3_ptr[NFSSRV_SHR_WRITE]);
+        default:
+                return (NULL);
+        }
+}
+
+kstat_t *
+exp_kstats_v4(struct exp_kstats *exp_kstats, uint_t op)
+{
+        if (exp_kstats == NULL)
+                return (NULL);
+
+        /* per share kstats for selected operations (read, write) only */
+        switch (op) {
+        case OP_READ:
+                return (exp_kstats->rfsshr_v4_ptr[NFSSRV_SHR_READ]);
+        case OP_WRITE:
+                return (exp_kstats->rfsshr_v4_ptr[NFSSRV_SHR_WRITE]);
+        default:
+                return (NULL);
+        }
 }