Viewing: shrinker_debug.c
// 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>
#include <linux/libcfs/libcfs_debug.h>
#include <linux/libcfs/libcfs_private.h>
#ifndef CONFIG_SHRINKER_DEBUG
/* RHEL7 is sooooo old and we really don't support it */
static DEFINE_IDA(shrinker_debugfs_ida);
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) {
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);
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;
sc.nr_scanned = nr_to_scan;
sc.nid = node_id;
sc.memcg = NULL;
shrinker->scan_objects(shrinker, &sc);
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, const char *name)
{
#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;
id = ida_alloc(&shrinker_debugfs_ida, GFP_KERNEL);
if (id < 0)
return id;
s->debugfs_id = id;
snprintf(buf, sizeof(buf), "%s-%d", name, 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);
}
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 */
if (s->debugfs_entry)
ida_free(&shrinker_debugfs_ida, s->debugfs_id);
debugfs_remove_recursive(s->debugfs_entry);
#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);
struct shrinker *ll_shrinker_alloc(unsigned int flags, const char *fmt, ...)
{
struct shrinker *shrinker = NULL;
struct ll_shrinker *s = NULL;
#ifdef HAVE_SHRINKER_ALLOC
struct va_format vaf;
#endif
va_list args;
int rc = 0;
/* Only time we don't need ll_shrinker is with latest kernels
* that have the shrinker debugfs interface turned on.
*/
#if defined(HAVE_REGISTER_SHRINKER_FORMAT_NAMED) || !defined(HAVE_SHRINKER_ALLOC) || !defined(CONFIG_SHRINKER_DEBUG)
LIBCFS_ALLOC(s, sizeof(*s));
if (s) {
#ifdef HAVE_REGISTER_SHRINKER_FORMAT_NAMED
s->vaf.fmt = fmt;
s->vaf.va = &args;
#endif
} else {
return ERR_PTR(-ENOMEM);
}
#endif
va_start(args, fmt);
#ifdef HAVE_SHRINKER_ALLOC
vaf.fmt = fmt;
vaf.va = &args;
shrinker = shrinker_alloc(flags, "%pV", &vaf);
if (shrinker)
shrinker->private_data = s;
else
rc = -ENOMEM;
#else
shrinker = (struct shrinker *)s;
#endif
#ifndef CONFIG_SHRINKER_DEBUG
if (rc == 0) {
const char *name = kvasprintf_const(GFP_KERNEL, fmt, args);
if (strncmp(name, "ldlm_pools", strlen("ldlm_pools")) != 0)
rc = shrinker_add_debugfs(shrinker, name);
kfree(name);
}
#endif
va_end(args);
if (rc < 0) {
if (shrinker)
ll_shrinker_free(shrinker);
return ERR_PTR(rc);
}
shrinker->seeks = DEFAULT_SEEKS;
return shrinker;
}
EXPORT_SYMBOL(ll_shrinker_alloc);
void ll_shrinker_register(struct shrinker *shrinker)
{
#ifndef HAVE_SHRINKER_ALLOC
#ifdef HAVE_REGISTER_SHRINKER_FORMAT_NAMED
struct ll_shrinker *s = container_of(shrinker, struct ll_shrinker,
ll_shrinker);
#endif
int rc;
#ifdef HAVE_REGISTER_SHRINKER_FORMAT_NAMED
rc = register_shrinker(shrinker, "%pV", &s->vaf);
#else
rc = register_shrinker(shrinker);
#endif
#else
shrinker_register(shrinker);
#endif
}
EXPORT_SYMBOL(ll_shrinker_register);
#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 */