diff --git a/include/os/linux/spl/sys/kstat.h b/include/os/linux/spl/sys/kstat.h
index 305c411ddf..7809fda520 100644
--- a/include/os/linux/spl/sys/kstat.h
+++ b/include/os/linux/spl/sys/kstat.h
@@ -20,6 +20,10 @@
* You should have received a copy of the GNU General Public License along
* with the SPL. If not, see .
*/
+/*
+ * Copyright (c) 2024, Klara, Inc.
+ * Copyright (c) 2024, Syneto
+ */
#ifndef _SPL_KSTAT_H
#define _SPL_KSTAT_H
@@ -89,6 +93,8 @@ typedef struct kstat_module {
struct list_head ksm_module_list; /* module linkage */
struct list_head ksm_kstat_list; /* list of kstat entries */
struct proc_dir_entry *ksm_proc; /* proc entry */
+ struct kstat_module *ksm_parent; /* parent module in hierarchy */
+ uint_t ksm_nchildren; /* number of child modules */
} kstat_module_t;
typedef struct kstat_raw_ops {
diff --git a/module/os/linux/spl/spl-kstat.c b/module/os/linux/spl/spl-kstat.c
index ad553a73a6..10732d6090 100644
--- a/module/os/linux/spl/spl-kstat.c
+++ b/module/os/linux/spl/spl-kstat.c
@@ -26,6 +26,10 @@
* [1] https://illumos.org/man/1M/kstat
* [2] https://illumos.org/man/9f/kstat_create
*/
+/*
+ * Copyright (c) 2024, Klara, Inc.
+ * Copyright (c) 2024, Syneto
+ */
#include
#include
@@ -379,33 +383,72 @@ kstat_find_module(char *name)
return (NULL);
}
-static kstat_module_t *
-kstat_create_module(char *name)
-{
- kstat_module_t *module;
- struct proc_dir_entry *pde;
-
- pde = proc_mkdir(name, proc_spl_kstat);
- if (pde == NULL)
- return (NULL);
-
- module = kmem_alloc(sizeof (kstat_module_t), KM_SLEEP);
- module->ksm_proc = pde;
- strlcpy(module->ksm_name, name, KSTAT_STRLEN);
- INIT_LIST_HEAD(&module->ksm_kstat_list);
- list_add_tail(&module->ksm_module_list, &kstat_module_list);
-
- return (module);
-
-}
-
static void
kstat_delete_module(kstat_module_t *module)
{
ASSERT(list_empty(&module->ksm_kstat_list));
- remove_proc_entry(module->ksm_name, proc_spl_kstat);
+ ASSERT0(module->ksm_nchildren);
+
+ kstat_module_t *parent = module->ksm_parent;
+
+ char *p = module->ksm_name, *frag;
+ while (p != NULL && (frag = strsep(&p, "/"))) {}
+
+ remove_proc_entry(frag, parent ? parent->ksm_proc : proc_spl_kstat);
list_del(&module->ksm_module_list);
kmem_free(module, sizeof (kstat_module_t));
+
+ if (parent) {
+ parent->ksm_nchildren--;
+ if (parent->ksm_nchildren == 0 &&
+ list_empty(&parent->ksm_kstat_list))
+ kstat_delete_module(parent);
+ }
+}
+
+static kstat_module_t *
+kstat_create_module(char *name)
+{
+ char buf[KSTAT_STRLEN];
+ kstat_module_t *module, *parent;
+
+ (void) strlcpy(buf, name, KSTAT_STRLEN);
+
+ parent = NULL;
+ char *p = buf, *frag;
+ while ((frag = strsep(&p, "/")) != NULL) {
+ module = kstat_find_module(buf);
+ if (module == NULL) {
+ struct proc_dir_entry *pde = proc_mkdir(frag,
+ parent ? parent->ksm_proc : proc_spl_kstat);
+ if (pde == NULL) {
+ cmn_err(CE_WARN, "kstat_create('%s'): "
+ "module dir create failed", buf);
+ if (parent)
+ kstat_delete_module(parent);
+ return (NULL);
+ }
+
+ module = kmem_alloc(sizeof (kstat_module_t), KM_SLEEP);
+ module->ksm_proc = pde;
+ strlcpy(module->ksm_name, buf, KSTAT_STRLEN);
+ INIT_LIST_HEAD(&module->ksm_kstat_list);
+ list_add_tail(&module->ksm_module_list,
+ &kstat_module_list);
+
+ if (parent != NULL) {
+ module->ksm_parent = parent;
+ parent->ksm_nchildren++;
+ }
+ }
+
+ parent = module;
+ if (p != NULL && p > frag)
+ p[-1] = '/';
+ }
+
+ return (module);
+
}
static int