]> git.baikalelectronics.ru Git - kernel.git/commitdiff
mm: shrinkers: introduce debugfs interface for memory shrinkers
authorRoman Gushchin <roman.gushchin@linux.dev>
Wed, 1 Jun 2022 03:22:23 +0000 (20:22 -0700)
committerakpm <akpm@linux-foundation.org>
Mon, 4 Jul 2022 01:08:40 +0000 (18:08 -0700)
This commit introduces the /sys/kernel/debug/shrinker debugfs interface
which provides an ability to observe the state of individual kernel memory
shrinkers.

Because the feature adds some memory overhead (which shouldn't be large
unless there is a huge amount of registered shrinkers), it's guarded by a
config option (enabled by default).

This commit introduces the "count" interface for each shrinker registered
in the system.

The output is in the following format:
<cgroup inode id> <nr of objects on node 0> <nr of objects on node 1>...
<cgroup inode id> <nr of objects on node 0> <nr of objects on node 1>...
...

To reduce the size of output on machines with many thousands cgroups, if
the total number of objects on all nodes is 0, the line is omitted.

If the shrinker is not memcg-aware or CONFIG_MEMCG is off, 0 is printed as
cgroup inode id.  If the shrinker is not numa-aware, 0's are printed for
all nodes except the first one.

This commit gives debugfs entries simple numeric names, which are not very
convenient.  The following commit in the series will provide shrinkers
with more meaningful names.

[akpm@linux-foundation.org: remove WARN_ON_ONCE(), per Roman]
Reported-by: syzbot+300d27c79fe6d4cbcc39@syzkaller.appspotmail.com
Link: https://lkml.kernel.org/r/20220601032227.4076670-3-roman.gushchin@linux.dev
Signed-off-by: Roman Gushchin <roman.gushchin@linux.dev>
Reviewed-by: Kent Overstreet <kent.overstreet@gmail.com>
Acked-by: Muchun Song <songmuchun@bytedance.com>
Cc: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
Cc: Dave Chinner <dchinner@redhat.com>
Cc: Hillf Danton <hdanton@sina.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
include/linux/shrinker.h
lib/Kconfig.debug
mm/Makefile
mm/shrinker_debug.c [new file with mode: 0644]
mm/vmscan.c

index 76fbf92b04d958c5d5ba23226c3651044d0e4fac..2ced8149c51371027fe7fe37480608aa9b26710e 100644 (file)
@@ -72,6 +72,10 @@ struct shrinker {
 #ifdef CONFIG_MEMCG
        /* ID in shrinker_idr */
        int id;
+#endif
+#ifdef CONFIG_SHRINKER_DEBUG
+       int debugfs_id;
+       struct dentry *debugfs_entry;
 #endif
        /* objs pending delete, per node */
        atomic_long_t *nr_deferred;
@@ -94,4 +98,17 @@ extern int register_shrinker(struct shrinker *shrinker);
 extern void unregister_shrinker(struct shrinker *shrinker);
 extern void free_prealloced_shrinker(struct shrinker *shrinker);
 extern void synchronize_shrinkers(void);
-#endif
+
+#ifdef CONFIG_SHRINKER_DEBUG
+extern int shrinker_debugfs_add(struct shrinker *shrinker);
+extern void shrinker_debugfs_remove(struct shrinker *shrinker);
+#else /* CONFIG_SHRINKER_DEBUG */
+static inline int shrinker_debugfs_add(struct shrinker *shrinker)
+{
+       return 0;
+}
+static inline void shrinker_debugfs_remove(struct shrinker *shrinker)
+{
+}
+#endif /* CONFIG_SHRINKER_DEBUG */
+#endif /* _LINUX_SHRINKER_H */
index 2e24db4bff1921483ad1bd1e181d242e6cd55345..0b483a8da409d53f85a3000e72d3e72a99aa7acc 100644 (file)
@@ -699,6 +699,15 @@ config DEBUG_OBJECTS_ENABLE_DEFAULT
        help
          Debug objects boot parameter default value
 
+config SHRINKER_DEBUG
+       default y
+       bool "Enable shrinker debugging support"
+       depends on DEBUG_FS
+       help
+         Say Y to enable the shrinker debugfs interface which provides
+         visibility into the kernel memory shrinkers subsystem.
+         Disable it to avoid an extra memory footprint.
+
 config HAVE_DEBUG_KMEMLEAK
        bool
 
index 6f9ffa968a1a13e9e303d8e7fb1a038b4c477f40..9a564f836403597c16fe1bddafc5aa91d7e25d99 100644 (file)
@@ -133,3 +133,4 @@ obj-$(CONFIG_PAGE_REPORTING) += page_reporting.o
 obj-$(CONFIG_IO_MAPPING) += io-mapping.o
 obj-$(CONFIG_HAVE_BOOTMEM_INFO_NODE) += bootmem_info.o
 obj-$(CONFIG_GENERIC_IOREMAP) += ioremap.o
+obj-$(CONFIG_SHRINKER_DEBUG) += shrinker_debug.o
diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
new file mode 100644 (file)
index 0000000..1a70556
--- /dev/null
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/idr.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/shrinker.h>
+#include <linux/memcontrol.h>
+
+/* defined in vmscan.c */
+extern struct rw_semaphore shrinker_rwsem;
+extern struct list_head shrinker_list;
+
+static DEFINE_IDA(shrinker_debugfs_ida);
+static struct dentry *shrinker_debugfs_root;
+
+static unsigned long shrinker_count_objects(struct shrinker *shrinker,
+                                           struct mem_cgroup *memcg,
+                                           unsigned long *count_per_node)
+{
+       unsigned long nr, total = 0;
+       int nid;
+
+       for_each_node(nid) {
+               if (nid == 0 || (shrinker->flags & SHRINKER_NUMA_AWARE)) {
+                       struct shrink_control sc = {
+                               .gfp_mask = GFP_KERNEL,
+                               .nid = nid,
+                               .memcg = memcg,
+                       };
+
+                       nr = shrinker->count_objects(shrinker, &sc);
+                       if (nr == SHRINK_EMPTY)
+                               nr = 0;
+               } else {
+                       nr = 0;
+               }
+
+               count_per_node[nid] = 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;
+       struct mem_cgroup *memcg;
+       unsigned long total;
+       bool memcg_aware;
+       int ret, nid;
+
+       count_per_node = kcalloc(nr_node_ids, sizeof(unsigned long), GFP_KERNEL);
+       if (!count_per_node)
+               return -ENOMEM;
+
+       ret = down_read_killable(&shrinker_rwsem);
+       if (ret) {
+               kfree(count_per_node);
+               return ret;
+       }
+       rcu_read_lock();
+
+       memcg_aware = shrinker->flags & SHRINKER_MEMCG_AWARE;
+
+       memcg = mem_cgroup_iter(NULL, NULL, NULL);
+       do {
+               if (memcg && !mem_cgroup_online(memcg))
+                       continue;
+
+               total = shrinker_count_objects(shrinker,
+                                              memcg_aware ? memcg : NULL,
+                                              count_per_node);
+               if (total) {
+                       seq_printf(m, "%lu", mem_cgroup_ino(memcg));
+                       for_each_node(nid)
+                               seq_printf(m, " %lu", count_per_node[nid]);
+                       seq_putc(m, '\n');
+               }
+
+               if (!memcg_aware) {
+                       mem_cgroup_iter_break(NULL, memcg);
+                       break;
+               }
+
+               if (signal_pending(current)) {
+                       mem_cgroup_iter_break(NULL, memcg);
+                       ret = -EINTR;
+                       break;
+               }
+       } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL);
+
+       rcu_read_unlock();
+       up_read(&shrinker_rwsem);
+
+       kfree(count_per_node);
+       return ret;
+}
+DEFINE_SHOW_ATTRIBUTE(shrinker_debugfs_count);
+
+int shrinker_debugfs_add(struct shrinker *shrinker)
+{
+       struct dentry *entry;
+       char buf[16];
+       int id;
+
+       lockdep_assert_held(&shrinker_rwsem);
+
+       /* debugfs isn't initialized yet, add debugfs entries later. */
+       if (!shrinker_debugfs_root)
+               return 0;
+
+       id = ida_alloc(&shrinker_debugfs_ida, GFP_KERNEL);
+       if (id < 0)
+               return id;
+       shrinker->debugfs_id = id;
+
+       snprintf(buf, sizeof(buf), "%d", id);
+
+       /* create debugfs entry */
+       entry = debugfs_create_dir(buf, shrinker_debugfs_root);
+       if (IS_ERR(entry)) {
+               ida_free(&shrinker_debugfs_ida, id);
+               return PTR_ERR(entry);
+       }
+       shrinker->debugfs_entry = entry;
+
+       debugfs_create_file("count", 0220, entry, shrinker,
+                           &shrinker_debugfs_count_fops);
+       return 0;
+}
+
+void shrinker_debugfs_remove(struct shrinker *shrinker)
+{
+       lockdep_assert_held(&shrinker_rwsem);
+
+       if (!shrinker->debugfs_entry)
+               return;
+
+       debugfs_remove_recursive(shrinker->debugfs_entry);
+       ida_free(&shrinker_debugfs_ida, shrinker->debugfs_id);
+}
+
+static int __init shrinker_debugfs_init(void)
+{
+       struct shrinker *shrinker;
+       struct dentry *dentry;
+       int ret = 0;
+
+       dentry = debugfs_create_dir("shrinker", NULL);
+       if (IS_ERR(dentry))
+               return PTR_ERR(dentry);
+       shrinker_debugfs_root = dentry;
+
+       /* Create debugfs entries for shrinkers registered at boot */
+       down_write(&shrinker_rwsem);
+       list_for_each_entry(shrinker, &shrinker_list, list)
+               if (!shrinker->debugfs_entry) {
+                       ret = shrinker_debugfs_add(shrinker);
+                       if (ret)
+                               break;
+               }
+       up_write(&shrinker_rwsem);
+
+       return ret;
+}
+late_initcall(shrinker_debugfs_init);
index f7d9a683e3a7d38fe1ffd0265b9f7d7acad938b4..35dedff79eb4cf6b2443da9f7a1008312f773535 100644 (file)
@@ -190,8 +190,8 @@ static void set_task_reclaim_state(struct task_struct *task,
        task->reclaim_state = rs;
 }
 
-static LIST_HEAD(shrinker_list);
-static DECLARE_RWSEM(shrinker_rwsem);
+LIST_HEAD(shrinker_list);
+DECLARE_RWSEM(shrinker_rwsem);
 
 #ifdef CONFIG_MEMCG
 static int shrinker_nr_max;
@@ -650,6 +650,7 @@ void register_shrinker_prepared(struct shrinker *shrinker)
        down_write(&shrinker_rwsem);
        list_add_tail(&shrinker->list, &shrinker_list);
        shrinker->flags |= SHRINKER_REGISTERED;
+       shrinker_debugfs_add(shrinker);
        up_write(&shrinker_rwsem);
 }
 
@@ -677,6 +678,7 @@ void unregister_shrinker(struct shrinker *shrinker)
        shrinker->flags &= ~SHRINKER_REGISTERED;
        if (shrinker->flags & SHRINKER_MEMCG_AWARE)
                unregister_memcg_shrinker(shrinker);
+       shrinker_debugfs_remove(shrinker);
        up_write(&shrinker_rwsem);
 
        kfree(shrinker->nr_deferred);