Whamcloud - gitweb
LU-18175 lustre: back port shrinker debugfs support. 74/56174/40
authorJames Simmons <jsimmons@infradead.org>
Mon, 7 Apr 2025 13:33:22 +0000 (09:33 -0400)
committerOleg Drokin <green@whamcloud.com>
Wed, 14 May 2025 03:53:44 +0000 (03:53 +0000)
The Linux kernel has developed a shrinker API to help deal
with a system that under memory pressure. Lustre uses this
shrinker API for several subsystems. While it helps this
API lacked the ability to manually trigger a cleanup. Now
with newer kernels a debugfs interface has been developed
just for this purpose. Back port this interface so Lustre
adminstrators can use this new feature to lighten the
load related to Lustre.

The following link explains this new debugfs interface:

https://lwn.net/Articles/896274

and how to use it:

https://docs.kernel.org/admin-guide/mm/shrinker_debugfs.html

For older kernels since Lustre shrinkers are not memcg aware
we remove that code since older kernels lack newer memcg
features. Sadly memcg functionality is not exported so
even for newer kernels we can't support it.

Note this is the basic functionality present in newer
kernels. A follow on patch will provide support in our
tools for this.

Test-Parameter: trivial
Signed-off-by: James Simmons <jsimmons@infradead.org>
Change-Id: I70aeb4af15c46036e51e362a69602babfd5ea05f
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/56174
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
Reviewed-by: Timothy Day <timday@amazon.com>
Reviewed-by: Brian Atkinson <batkinson@lanl.gov>
12 files changed:
include/lustre_compat/linux/shrinker.h [new file with mode: 0644]
libcfs/include/libcfs/linux/linux-misc.h
libcfs/libcfs/Makefile.in
libcfs/libcfs/linux/linux-prim.c
lustre/include/lustre_compat.h
lustre/ldlm/ldlm_pool.c
lustre/obdclass/lu_object.c
lustre/obdclass/page_pools.c
lustre/osc/osc_request.c
lustre_compat/lib/Makefile
lustre_compat/mm/Makefile [new file with mode: 0644]
lustre_compat/mm/shrinker_debug.c [new file with mode: 0644]

diff --git a/include/lustre_compat/linux/shrinker.h b/include/lustre_compat/linux/shrinker.h
new file mode 100644 (file)
index 0000000..9d73079
--- /dev/null
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_SHRINKER_LUSTRE_H
+#define _LINUX_SHRINKER_LUSTRE_H
+
+#include <linux/atomic.h>
+#include <linux/mm.h>
+#include <linux/shrinker.h>
+#include <linux/types.h>
+#include <libcfs/libcfs.h>
+
+struct ll_shrinker_ops {
+#ifdef HAVE_SHRINKER_COUNT
+       unsigned long (*count_objects)(struct shrinker *shrinker,
+                                      struct shrink_control *sc);
+       unsigned long (*scan_objects)(struct shrinker *shrinker,
+                                     struct shrink_control *sc);
+#else
+       int (*shrink)(struct shrinker *shrinker, struct shrink_control *sc);
+#endif
+       int seeks;      /* seeks to recreate an obj */
+};
+
+#ifndef CONFIG_SHRINKER_DEBUG
+struct ll_shrinker {
+       struct shrinker ll_shrinker;
+
+       void *private_data;
+
+       int debugfs_id;
+       const char *name;
+       struct dentry *debugfs_entry;
+};
+#endif
+
+void ll_shrinker_free(struct shrinker *shrinker);
+
+/* allocate and register a shrinker, return should be checked with IS_ERR() */
+struct shrinker *ll_shrinker_create(struct ll_shrinker_ops *ops,
+                                   unsigned int flags,
+                                   const char *fmt, ...);
+
+#endif /* _LINUX_SHRINKER_LUSTRE_H */
index 8e488ae..43228e4 100644 (file)
@@ -346,4 +346,12 @@ void bitmap_to_arr32(u32 *buf, const unsigned long *bitmap, unsigned int nbits);
 #define KOBJ_ATTRIBUTE_GROUPS(_name)   ATTRIBUTE_GROUPS(_name)
 #endif
 
+#ifndef CONFIG_SHRINKER_DEBUG
+void shrinker_debugfs_fini(void);
+int shrinker_debugfs_init(void);
+#else
+static inline void shrinker_debugfs_fini(void) {};
+static inline int shrinker_debugfs_init(void) { return 0; };
+#endif
+
 #endif /* __LIBCFS_LINUX_MISC_H__ */
index 0984486..3692050 100644 (file)
@@ -6,16 +6,22 @@
 
 MODULES = libcfs
 
-COMPAT := @top_srcdir@/lustre_compat/lib/
 
 libcfs_dir := $(dir $(lastword $(MAKEFILE_LIST)))
+
+libcfs-compat-objs :=
+
+COMPAT_MM := @top_srcdir@/lustre_compat/mm/
+include $(libcfs_dir)/../../lustre_compat/mm/Makefile
+libcfs-compat-objs += $(patsubst %,$(COMPAT_MM)%,$(mm_objs))
+
+COMPAT_LIB := @top_srcdir@/lustre_compat/lib/
 include $(libcfs_dir)/../../lustre_compat/lib/Makefile
+libcfs-compat-objs += $(patsubst %,$(COMPAT_LIB)%,$(lib_objs))
 
 libcfs-linux-objs := linux-prim.o
 libcfs-linux-objs += linux-wait.o
 
-libcfs-compat-objs += $(patsubst %,$(COMPAT)%,$(compat_objs))
-
 EXTRA_DIST = $(libcfs-compat-objs:.o=.c)
 
 libcfs-crypto-objs := crypto.o fname.o hkdf.o hooks.o keyring.o
index ee2c0ba..d50db3d 100644 (file)
@@ -188,6 +188,8 @@ void __init init_libcfs_vfree_atomic(void)
 
 int __init cfs_arch_init(void)
 {
+       int rc = 0;
+
        init_libcfs_vfree_atomic();
 
 #ifndef HAVE_WAIT_VAR_EVENT
@@ -205,7 +207,26 @@ int __init cfs_arch_init(void)
                                          SLAB_PANIC | SLAB_RECLAIM_ACCOUNT,
                                          xarray_node_ctor);
 #endif
-       return llcrypt_init();
+       rc = shrinker_debugfs_init();
+       if (rc < 0)
+               goto free_xcache;
+
+#ifdef CONFIG_LL_ENCRYPTION
+       rc = llcrypt_init();
+       if (rc < 0)
+               goto free_shrinker;
+#endif
+       return rc;
+
+#ifdef CONFIG_LL_ENCRYPTION
+free_shrinker:
+       shrinker_debugfs_fini();
+#endif
+free_xcache:
+#ifndef HAVE_XARRAY_SUPPORT
+       kmem_cache_destroy(xarray_cachep);
+#endif
+       return rc;
 }
 
 void __exit cfs_arch_exit(void)
@@ -213,7 +234,13 @@ void __exit cfs_arch_exit(void)
        /* exit_libcfs_vfree_atomic */
        __flush_workqueue(system_wq);
 
+#ifndef HAVE_XARRAY_SUPPORT
+       kmem_cache_destroy(xarray_cachep);
+#endif
+       shrinker_debugfs_fini();
+#ifdef CONFIG_LL_ENCRYPTION
        llcrypt_exit();
+#endif
 }
 
 int cfs_kernel_write(struct file *filp, const void *buf, size_t count,
index 7a98d63..144fa9c 100644 (file)
@@ -29,6 +29,7 @@
 #include <linux/pagevec.h>
 #include <linux/workqueue.h>
 #include <libcfs/linux/linux-fs.h>
+#include <lustre_compat/linux/shrinker.h>
 #include <lustre_compat/linux/xarray.h>
 #include <obd_support.h>
 
@@ -657,83 +658,20 @@ static inline ssize_t iov_iter_get_pages_alloc2(struct iov_iter *i,
 #define migrate_folio  migratepage
 #endif
 
-struct ll_shrinker_ops {
-#ifdef HAVE_SHRINKER_COUNT
-       unsigned long (*count_objects)(struct shrinker *,
-                                      struct shrink_control *sc);
-       unsigned long (*scan_objects)(struct shrinker *,
-                                     struct shrink_control *sc);
-#else
-       int (*shrink)(struct shrinker *, struct shrink_control *sc);
-#endif
-       int seeks;      /* seeks to recreate an obj */
-};
-
-#ifndef HAVE_SHRINKER_ALLOC
-static inline void shrinker_free(struct shrinker *shrinker)
+static inline const char *shrinker_debugfs_path(struct shrinker *shrinker)
 {
-       unregister_shrinker(shrinker);
-       OBD_FREE_PTR(shrinker);
-}
-#endif
-
-/* allocate and register a shrinker, return should be checked with IS_ERR() */
-static inline struct shrinker *
-ll_shrinker_create(struct ll_shrinker_ops *ops, unsigned int flags,
-                  const char *fmt, ...)
-{
-       struct shrinker *shrinker;
-       int rc = 0;
-
-#if defined(HAVE_REGISTER_SHRINKER_FORMAT_NAMED) || defined(HAVE_SHRINKER_ALLOC)
-       struct va_format vaf;
-       va_list args;
-#endif
-
-#ifdef HAVE_SHRINKER_ALLOC
-       va_start(args, fmt);
-       vaf.fmt = fmt;
-       vaf.va = &args;
-       shrinker = shrinker_alloc(flags, "%pV", &vaf);
-       va_end(args);
-#else
-       OBD_ALLOC_PTR(shrinker);
-#endif
-       if (!shrinker)
-               return ERR_PTR(-ENOMEM);
-
-#ifdef HAVE_SHRINKER_COUNT
-       shrinker->count_objects = ops->count_objects;
-       shrinker->scan_objects = ops->scan_objects;
-#else
-       shrinker->shrink = ops->shrink;
-#endif
-       shrinker->seeks = ops->seeks;
-
-#ifdef HAVE_SHRINKER_ALLOC
-       shrinker_register(shrinker);
-#else
- #ifdef HAVE_REGISTER_SHRINKER_FORMAT_NAMED
-       va_start(args, fmt);
-       vaf.fmt = fmt;
-       vaf.va = &args;
-       rc = register_shrinker(shrinker, "%pV", &vaf);
-       va_end(args);
- #elif defined(HAVE_REGISTER_SHRINKER_RET)
-       rc = register_shrinker(shrinker);
+#ifndef CONFIG_SHRINKER_DEBUG
+ #ifndef HAVE_SHRINKER_ALLOC
+       struct ll_shrinker *s = container_of(shrinker, struct ll_shrinker,
+                                            ll_shrinker);
  #else
-       register_shrinker(shrinker);
+       struct ll_shrinker *s = shrinker->private_data;
  #endif
-#endif
-       if (rc) {
-#ifdef HAVE_SHRINKER_ALLOC
-               shrinker_free(shrinker);
-#else
-               OBD_FREE_PTR(shrinker);
-#endif
-               shrinker = ERR_PTR(rc);
-       }
-       return shrinker;
+#else /* !CONFIG_SHRINKER_DEBUG */
+       struct shrinker *s = shrinker;
+#endif /* CONFIG_SHRINKER_DEBUG */
+
+       return s->debugfs_entry->d_name.name;
 }
 
 #ifndef fallthrough
index 39cdc6f..4cb4ba3 100644 (file)
@@ -74,6 +74,7 @@
 #define DEBUG_SUBSYSTEM S_LDLM
 
 #include <linux/workqueue.h>
+#include <lustre_compat/linux/shrinker.h>
 #include <libcfs/linux/linux-mem.h>
 #include <lustre_dlm.h>
 #include <cl_object.h>
@@ -1434,7 +1435,7 @@ int ldlm_pools_init(void)
        RETURN(0);
 
 out_shrinker:
-       shrinker_free(ldlm_pools_srv_shrinker);
+       ll_shrinker_free(ldlm_pools_srv_shrinker);
 out:
        RETURN(rc);
 }
@@ -1444,8 +1445,8 @@ void ldlm_pools_fini(void)
        if (ldlm_pools_init_done) {
                cancel_delayed_work_sync(&ldlm_pools_recalc_work);
 
-               shrinker_free(ldlm_pools_srv_shrinker);
-               shrinker_free(ldlm_pools_cli_shrinker);
+               ll_shrinker_free(ldlm_pools_srv_shrinker);
+               ll_shrinker_free(ldlm_pools_cli_shrinker);
        }
 
        ldlm_pools_init_done = false;
index b4cf710..032711d 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/list.h>
 #include <linux/processor.h>
 #include <linux/random.h>
+#include <lustre_compat/linux/shrinker.h>
 
 #include <lustre_compat/linux/rhashtable.h>
 
@@ -2167,7 +2168,7 @@ int lu_global_init(void)
        return result;
 
 out_shrinker:
-       shrinker_free(lu_site_shrinker);
+       ll_shrinker_free(lu_site_shrinker);
 out_env:
        /* ordering here is explained in lu_global_fini() */
        lu_context_key_degister(&lu_global_key);
@@ -2181,7 +2182,7 @@ out:
 /* Dual to lu_global_init(). */
 void lu_global_fini(void)
 {
-       shrinker_free(lu_site_shrinker);
+       ll_shrinker_free(lu_site_shrinker);
 
        lu_context_key_degister(&lu_global_key);
 
index 6be8b7e..edc4b0b 100644 (file)
@@ -15,6 +15,7 @@
 
 #define DEBUG_SUBSYSTEM S_SEC
 
+#include <lustre_compat/linux/shrinker.h>
 #include <libcfs/linux/linux-mem.h>
 
 #include <obd.h>
@@ -1145,7 +1146,7 @@ void obd_pool_fini(void)
 
        for (pool_order = 0; pool_order < pools_count; pool_order++) {
                pool = page_pools[pool_order];
-               shrinker_free(pool->pool_shrinker);
+               ll_shrinker_free(pool->pool_shrinker);
                LASSERT(pool->opp_ptr_pages);
                LASSERT(pool->opp_total_objects == pool->opp_free_objects);
 
index c5a1daf..736cc7c 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/workqueue.h>
 #include <libcfs/libcfs.h>
 #include <linux/falloc.h>
+#include <lustre_compat/linux/shrinker.h>
 #include <lprocfs_status.h>
 #include <lustre_dlm.h>
 #include <lustre_fid.h>
@@ -4181,7 +4182,7 @@ out_stop_grant:
 out_req_pool:
        ptlrpc_free_rq_pool(osc_rq_pool);
 out_shrinker:
-       shrinker_free(osc_cache_shrinker);
+       ll_shrinker_free(osc_cache_shrinker);
 out_kmem:
        lu_kmem_fini(osc_caches);
 
@@ -4193,7 +4194,7 @@ static void __exit osc_exit(void)
        class_unregister_type(LUSTRE_OSC_NAME);
        ptlrpc_free_rq_pool(osc_rq_pool);
        osc_stop_grant_work();
-       shrinker_free(osc_cache_shrinker);
+       ll_shrinker_free(osc_cache_shrinker);
        lu_kmem_fini(osc_caches);
 }
 
index 6a36e8b..3be7ee0 100644 (file)
@@ -4,4 +4,4 @@
 # This file is part of Lustre, http://www.lustre.org/
 #
 
-compat_objs := xarray.o glob.o generic-radix-tree.o
+lib_objs := xarray.o glob.o generic-radix-tree.o
diff --git a/lustre_compat/mm/Makefile b/lustre_compat/mm/Makefile
new file mode 100644 (file)
index 0000000..36451bf
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+#
+# This file is part of Lustre, http://www.lustre.org/
+#
+
+mm_objs := shrinker_debug.o
diff --git a/lustre_compat/mm/shrinker_debug.c b/lustre_compat/mm/shrinker_debug.c
new file mode 100644 (file)
index 0000000..55d2ea3
--- /dev/null
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0
+/* This is taken from kernel commit:
+ *
+ * 8a0e8bb11 ("mm: shrinker: convert shrinker_rwsem to mutex")
+ *
+ * at kernel verison 6.6-rc4
+ */
+#include <linux/idr.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/memcontrol.h>
+#include <lustre_compat/linux/shrinker.h>
+
+#ifndef CONFIG_SHRINKER_DEBUG
+/* RHEL7 is sooooo old and we really don't support it */
+#ifdef HAVE_SHRINKER_COUNT
+static DEFINE_IDA(shrinker_debugfs_ida);
+#else
+static int seq;
+#endif
+static struct dentry *shrinker_debugfs_root;
+
+#ifndef SHRINK_EMPTY
+#define SHRINK_EMPTY (~0UL - 1)
+#endif
+
+static unsigned long shrinker_count_objects(struct shrinker *shrinker,
+                                           struct mem_cgroup *memcg,
+                                           unsigned long *count_per_node)
+{
+       unsigned long nr, total = 0;
+       int node_id;
+
+       for_each_node(node_id) {
+#ifdef HAVE_SHRINKER_COUNT
+               if (node_id == 0 || (shrinker->flags & SHRINKER_NUMA_AWARE)) {
+                       struct shrink_control sc = {
+                               .gfp_mask = GFP_KERNEL,
+                               .nid = node_id,
+                               .memcg = memcg,
+                       };
+
+                       nr = shrinker->count_objects(shrinker, &sc);
+#else
+               if (node_id == 0) {
+                       struct shrink_control sc = {
+                               .gfp_mask = GFP_KERNEL,
+                       };
+
+                       nr = shrinker->shrink(shrinker, &sc);
+#endif
+                       if (nr == SHRINK_EMPTY)
+                               nr = 0;
+               } else {
+                       nr = 0;
+               }
+
+               count_per_node[node_id] = nr;
+               total += nr;
+       }
+
+       return total;
+}
+
+static int shrinker_debugfs_count_show(struct seq_file *m, void *v)
+{
+       struct shrinker *shrinker = m->private;
+       unsigned long *count_per_node;
+       unsigned long total;
+       int node_id;
+
+       count_per_node = kcalloc(nr_node_ids, sizeof(unsigned long),
+                                GFP_KERNEL);
+       if (!count_per_node)
+               return -ENOMEM;
+
+       rcu_read_lock();
+
+       /* Lustre shrinker's don't support memcg aware shrinkers so
+        * we simplify this code for older platforms. Sadly newer
+        * kernels don't export the memcg functions we need so even
+        * for the latest kernels we can't support memcg.
+        */
+       total = shrinker_count_objects(shrinker, NULL, count_per_node);
+       if (total) {
+               /* Lustre doesn't support memcg aware shrinkers
+                * so just print 0
+                */
+               seq_putc(m, '0');
+               for_each_node(node_id)
+                       seq_printf(m, " %lu", count_per_node[node_id]);
+               seq_putc(m, '\n');
+       }
+
+       rcu_read_unlock();
+
+       kfree(count_per_node);
+       return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(shrinker_debugfs_count);
+
+static int shrinker_debugfs_scan_open(struct inode *inode, struct file *file)
+{
+       file->private_data = inode->i_private;
+       return nonseekable_open(inode, file);
+}
+
+static ssize_t shrinker_debugfs_scan_write(struct file *file,
+                                          const char __user *buf,
+                                          size_t size, loff_t *pos)
+{
+       struct shrinker *shrinker = file->private_data;
+       unsigned long nr_to_scan = 0, ino, read_len;
+       struct shrink_control sc = {
+               .gfp_mask = GFP_KERNEL,
+       };
+       char kbuf[72];
+       int node_id;
+
+       read_len = size < (sizeof(kbuf) - 1) ? size : (sizeof(kbuf) - 1);
+       if (copy_from_user(kbuf, buf, read_len))
+               return -EFAULT;
+       kbuf[read_len] = '\0';
+
+       if (sscanf(kbuf, "%lu %d %lu", &ino, &node_id, &nr_to_scan) != 3)
+               return -EINVAL;
+
+       if (node_id < 0 || node_id >= nr_node_ids)
+               return -EINVAL;
+
+       if (nr_to_scan == 0)
+               return size;
+
+       /* Lustre doesn't support memcg aware shrinkers */
+       if (ino != 0)
+               return -EINVAL;
+
+       sc.nr_to_scan = nr_to_scan;
+#ifdef HAVE_SHRINKER_COUNT
+       sc.nr_scanned = nr_to_scan;
+       sc.nid = node_id;
+       sc.memcg = NULL;
+
+       shrinker->scan_objects(shrinker, &sc);
+#else
+       shrinker->shrink(shrinker, &sc);
+#endif
+       return size;
+}
+
+static const struct file_operations shrinker_debugfs_scan_fops = {
+       .owner  = THIS_MODULE,
+       .open   = shrinker_debugfs_scan_open,
+       .write  = shrinker_debugfs_scan_write,
+};
+
+static int shrinker_add_debugfs(struct shrinker *shrinker)
+{
+#ifndef HAVE_SHRINKER_ALLOC
+       struct ll_shrinker *s = container_of(shrinker, struct ll_shrinker,
+                                            ll_shrinker);
+#else
+       struct ll_shrinker *s = shrinker->private_data;
+#endif
+       struct dentry *entry;
+       char buf[128];
+       int id;
+
+       /* debugfs isn't initialized yet, add debugfs entries later. */
+       if (!shrinker_debugfs_root)
+               return 0;
+
+#ifdef HAVE_SHRINKER_COUNT
+       id = ida_alloc(&shrinker_debugfs_ida, GFP_KERNEL);
+       if (id < 0)
+               return id;
+#else
+       id = seq++;
+#endif
+       s->debugfs_id = id;
+
+       snprintf(buf, sizeof(buf), "%s-%d", s->name, id);
+
+       /* create debugfs entry */
+       entry = debugfs_create_dir(buf, shrinker_debugfs_root);
+       if (IS_ERR(entry)) {
+#ifdef HAVE_SHRINKER_COUNT
+               ida_free(&shrinker_debugfs_ida, id);
+#else
+               seq--;
+#endif
+               return PTR_ERR(entry);
+       }
+       s->debugfs_entry = entry;
+
+       debugfs_create_file("count", 0440, entry, shrinker,
+                           &shrinker_debugfs_count_fops);
+       debugfs_create_file("scan", 0220, entry, shrinker,
+                           &shrinker_debugfs_scan_fops);
+       return 0;
+}
+#endif /* !CONFIG_SHRINKER_DEBUG */
+
+void ll_shrinker_free(struct shrinker *shrinker)
+{
+#ifndef CONFIG_SHRINKER_DEBUG
+#ifndef HAVE_SHRINKER_ALLOC
+       struct ll_shrinker *s = container_of(shrinker, struct ll_shrinker,
+                                            ll_shrinker);
+#else
+       struct ll_shrinker *s = shrinker->private_data;
+#endif /* HAVE_SHRINKER_ALLOC */
+
+#ifdef HAVE_SHRINKER_COUNT
+       if (s->debugfs_entry)
+               ida_free(&shrinker_debugfs_ida, s->debugfs_id);
+#endif
+       debugfs_remove_recursive(s->debugfs_entry);
+       kfree(s->name);
+#endif /* !CONFIG_SHRINKER_DEBUG */
+
+#ifdef HAVE_SHRINKER_ALLOC
+       shrinker_free(shrinker);
+#else /* !HAVE_SHRINKER_ALLOC */
+       unregister_shrinker(shrinker);
+#endif /* !HAVE_SHRINKER_ALLOC */
+
+#ifndef CONFIG_SHRINKER_DEBUG
+       LIBCFS_FREE_PRE(s, sizeof(*s), "kfreed");
+       kfree(s);
+#endif
+}
+EXPORT_SYMBOL(ll_shrinker_free);
+
+/* allocate and register a shrinker, return should be checked with IS_ERR() */
+struct shrinker *ll_shrinker_create(struct ll_shrinker_ops *ops,
+                                   unsigned int flags,
+                                   const char *fmt, ...)
+{
+       struct shrinker *shrinker;
+#ifndef CONFIG_SHRINKER_DEBUG
+       struct ll_shrinker *s;
+#endif
+#if defined(HAVE_REGISTER_SHRINKER_FORMAT_NAMED) || defined(HAVE_SHRINKER_ALLOC)
+       struct va_format vaf;
+#endif
+       va_list args;
+       int rc = 0;
+
+       va_start(args, fmt);
+#ifdef HAVE_SHRINKER_ALLOC
+       vaf.fmt = fmt;
+       vaf.va = &args;
+       shrinker = shrinker_alloc(flags, "%pV", &vaf);
+       va_end(args);
+ #ifndef CONFIG_SHRINKER_DEBUG
+       LIBCFS_ALLOC(s, sizeof(*s));
+       if (!s) {
+               shrinker_free(shrinker);
+               shrinker = NULL;
+       } else {
+               s->name = kvasprintf_const(GFP_KERNEL, fmt, args);
+               shrinker->private_data = s;
+       }
+ #endif
+#else /* !HAVE_SHRINKER_ALLOC */
+       LIBCFS_ALLOC(s, sizeof(*s));
+ #if !defined(CONFIG_SHRINKER_DEBUG)
+       if (s)
+               s->name = kvasprintf_const(GFP_KERNEL, fmt, args);
+ #endif
+       va_end(args);
+       shrinker = (struct shrinker *)s;
+#endif
+       if (!shrinker)
+               return ERR_PTR(-ENOMEM);
+
+#ifdef HAVE_SHRINKER_COUNT
+       shrinker->count_objects = ops->count_objects;
+       shrinker->scan_objects = ops->scan_objects;
+#else
+       shrinker->shrink = ops->shrink;
+#endif
+       shrinker->seeks = ops->seeks;
+
+#ifdef HAVE_SHRINKER_ALLOC
+       shrinker_register(shrinker);
+#else
+ #ifdef HAVE_REGISTER_SHRINKER_FORMAT_NAMED
+       va_start(args, fmt);
+       vaf.fmt = fmt;
+       vaf.va = &args;
+       rc = register_shrinker(shrinker, "%pV", &vaf);
+       va_end(args);
+ #else
+       if (rc == 0) {
+  #if defined(HAVE_REGISTER_SHRINKER_RET)
+               rc = register_shrinker(shrinker);
+  #else
+               register_shrinker(shrinker);
+  #endif
+       }
+ #endif
+#endif
+  #ifndef CONFIG_SHRINKER_DEBUG
+       if (rc == 0 && s->name &&
+           strncmp(s->name, "ldlm_pools", strlen("ldlm_pools")) != 0)
+               rc = shrinker_add_debugfs(shrinker);
+  #endif
+       if (rc) {
+               ll_shrinker_free(shrinker);
+               shrinker = ERR_PTR(rc);
+       }
+       return shrinker;
+}
+EXPORT_SYMBOL(ll_shrinker_create);
+
+#ifndef CONFIG_SHRINKER_DEBUG
+void shrinker_debugfs_fini(void)
+{
+       debugfs_remove_recursive(shrinker_debugfs_root);
+}
+
+int __init shrinker_debugfs_init(void)
+{
+       struct dentry *dentry;
+       int ret = 0;
+
+       dentry = debugfs_create_dir("shrinker", NULL);
+       if (IS_ERR(dentry))
+               return PTR_ERR(dentry);
+
+       shrinker_debugfs_root = dentry;
+
+       return ret;
+}
+#endif /* CONFIG_SHRINKER_DEBUG */