162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci#include <linux/idr.h> 362306a36Sopenharmony_ci#include <linux/slab.h> 462306a36Sopenharmony_ci#include <linux/debugfs.h> 562306a36Sopenharmony_ci#include <linux/seq_file.h> 662306a36Sopenharmony_ci#include <linux/shrinker.h> 762306a36Sopenharmony_ci#include <linux/memcontrol.h> 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci/* defined in vmscan.c */ 1062306a36Sopenharmony_ciextern struct rw_semaphore shrinker_rwsem; 1162306a36Sopenharmony_ciextern struct list_head shrinker_list; 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_cistatic DEFINE_IDA(shrinker_debugfs_ida); 1462306a36Sopenharmony_cistatic struct dentry *shrinker_debugfs_root; 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistatic unsigned long shrinker_count_objects(struct shrinker *shrinker, 1762306a36Sopenharmony_ci struct mem_cgroup *memcg, 1862306a36Sopenharmony_ci unsigned long *count_per_node) 1962306a36Sopenharmony_ci{ 2062306a36Sopenharmony_ci unsigned long nr, total = 0; 2162306a36Sopenharmony_ci int nid; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci for_each_node(nid) { 2462306a36Sopenharmony_ci if (nid == 0 || (shrinker->flags & SHRINKER_NUMA_AWARE)) { 2562306a36Sopenharmony_ci struct shrink_control sc = { 2662306a36Sopenharmony_ci .gfp_mask = GFP_KERNEL, 2762306a36Sopenharmony_ci .nid = nid, 2862306a36Sopenharmony_ci .memcg = memcg, 2962306a36Sopenharmony_ci }; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci nr = shrinker->count_objects(shrinker, &sc); 3262306a36Sopenharmony_ci if (nr == SHRINK_EMPTY) 3362306a36Sopenharmony_ci nr = 0; 3462306a36Sopenharmony_ci } else { 3562306a36Sopenharmony_ci nr = 0; 3662306a36Sopenharmony_ci } 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci count_per_node[nid] = nr; 3962306a36Sopenharmony_ci total += nr; 4062306a36Sopenharmony_ci } 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci return total; 4362306a36Sopenharmony_ci} 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistatic int shrinker_debugfs_count_show(struct seq_file *m, void *v) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci struct shrinker *shrinker = m->private; 4862306a36Sopenharmony_ci unsigned long *count_per_node; 4962306a36Sopenharmony_ci struct mem_cgroup *memcg; 5062306a36Sopenharmony_ci unsigned long total; 5162306a36Sopenharmony_ci bool memcg_aware; 5262306a36Sopenharmony_ci int ret, nid; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci count_per_node = kcalloc(nr_node_ids, sizeof(unsigned long), GFP_KERNEL); 5562306a36Sopenharmony_ci if (!count_per_node) 5662306a36Sopenharmony_ci return -ENOMEM; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci ret = down_read_killable(&shrinker_rwsem); 5962306a36Sopenharmony_ci if (ret) { 6062306a36Sopenharmony_ci kfree(count_per_node); 6162306a36Sopenharmony_ci return ret; 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci rcu_read_lock(); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci memcg_aware = shrinker->flags & SHRINKER_MEMCG_AWARE; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci memcg = mem_cgroup_iter(NULL, NULL, NULL); 6862306a36Sopenharmony_ci do { 6962306a36Sopenharmony_ci if (memcg && !mem_cgroup_online(memcg)) 7062306a36Sopenharmony_ci continue; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci total = shrinker_count_objects(shrinker, 7362306a36Sopenharmony_ci memcg_aware ? memcg : NULL, 7462306a36Sopenharmony_ci count_per_node); 7562306a36Sopenharmony_ci if (total) { 7662306a36Sopenharmony_ci seq_printf(m, "%lu", mem_cgroup_ino(memcg)); 7762306a36Sopenharmony_ci for_each_node(nid) 7862306a36Sopenharmony_ci seq_printf(m, " %lu", count_per_node[nid]); 7962306a36Sopenharmony_ci seq_putc(m, '\n'); 8062306a36Sopenharmony_ci } 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci if (!memcg_aware) { 8362306a36Sopenharmony_ci mem_cgroup_iter_break(NULL, memcg); 8462306a36Sopenharmony_ci break; 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci if (signal_pending(current)) { 8862306a36Sopenharmony_ci mem_cgroup_iter_break(NULL, memcg); 8962306a36Sopenharmony_ci ret = -EINTR; 9062306a36Sopenharmony_ci break; 9162306a36Sopenharmony_ci } 9262306a36Sopenharmony_ci } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci rcu_read_unlock(); 9562306a36Sopenharmony_ci up_read(&shrinker_rwsem); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci kfree(count_per_node); 9862306a36Sopenharmony_ci return ret; 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(shrinker_debugfs_count); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic int shrinker_debugfs_scan_open(struct inode *inode, struct file *file) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci file->private_data = inode->i_private; 10562306a36Sopenharmony_ci return nonseekable_open(inode, file); 10662306a36Sopenharmony_ci} 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cistatic ssize_t shrinker_debugfs_scan_write(struct file *file, 10962306a36Sopenharmony_ci const char __user *buf, 11062306a36Sopenharmony_ci size_t size, loff_t *pos) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci struct shrinker *shrinker = file->private_data; 11362306a36Sopenharmony_ci unsigned long nr_to_scan = 0, ino, read_len; 11462306a36Sopenharmony_ci struct shrink_control sc = { 11562306a36Sopenharmony_ci .gfp_mask = GFP_KERNEL, 11662306a36Sopenharmony_ci }; 11762306a36Sopenharmony_ci struct mem_cgroup *memcg = NULL; 11862306a36Sopenharmony_ci int nid; 11962306a36Sopenharmony_ci char kbuf[72]; 12062306a36Sopenharmony_ci ssize_t ret; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci read_len = size < (sizeof(kbuf) - 1) ? size : (sizeof(kbuf) - 1); 12362306a36Sopenharmony_ci if (copy_from_user(kbuf, buf, read_len)) 12462306a36Sopenharmony_ci return -EFAULT; 12562306a36Sopenharmony_ci kbuf[read_len] = '\0'; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci if (sscanf(kbuf, "%lu %d %lu", &ino, &nid, &nr_to_scan) != 3) 12862306a36Sopenharmony_ci return -EINVAL; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci if (nid < 0 || nid >= nr_node_ids) 13162306a36Sopenharmony_ci return -EINVAL; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci if (nr_to_scan == 0) 13462306a36Sopenharmony_ci return size; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if (shrinker->flags & SHRINKER_MEMCG_AWARE) { 13762306a36Sopenharmony_ci memcg = mem_cgroup_get_from_ino(ino); 13862306a36Sopenharmony_ci if (!memcg || IS_ERR(memcg)) 13962306a36Sopenharmony_ci return -ENOENT; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci if (!mem_cgroup_online(memcg)) { 14262306a36Sopenharmony_ci mem_cgroup_put(memcg); 14362306a36Sopenharmony_ci return -ENOENT; 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci } else if (ino != 0) { 14662306a36Sopenharmony_ci return -EINVAL; 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci ret = down_read_killable(&shrinker_rwsem); 15062306a36Sopenharmony_ci if (ret) { 15162306a36Sopenharmony_ci mem_cgroup_put(memcg); 15262306a36Sopenharmony_ci return ret; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci sc.nid = nid; 15662306a36Sopenharmony_ci sc.memcg = memcg; 15762306a36Sopenharmony_ci sc.nr_to_scan = nr_to_scan; 15862306a36Sopenharmony_ci sc.nr_scanned = nr_to_scan; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci shrinker->scan_objects(shrinker, &sc); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci up_read(&shrinker_rwsem); 16362306a36Sopenharmony_ci mem_cgroup_put(memcg); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci return size; 16662306a36Sopenharmony_ci} 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_cistatic const struct file_operations shrinker_debugfs_scan_fops = { 16962306a36Sopenharmony_ci .owner = THIS_MODULE, 17062306a36Sopenharmony_ci .open = shrinker_debugfs_scan_open, 17162306a36Sopenharmony_ci .write = shrinker_debugfs_scan_write, 17262306a36Sopenharmony_ci}; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ciint shrinker_debugfs_add(struct shrinker *shrinker) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci struct dentry *entry; 17762306a36Sopenharmony_ci char buf[128]; 17862306a36Sopenharmony_ci int id; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci lockdep_assert_held(&shrinker_rwsem); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* debugfs isn't initialized yet, add debugfs entries later. */ 18362306a36Sopenharmony_ci if (!shrinker_debugfs_root) 18462306a36Sopenharmony_ci return 0; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci id = ida_alloc(&shrinker_debugfs_ida, GFP_KERNEL); 18762306a36Sopenharmony_ci if (id < 0) 18862306a36Sopenharmony_ci return id; 18962306a36Sopenharmony_ci shrinker->debugfs_id = id; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci /* create debugfs entry */ 19462306a36Sopenharmony_ci entry = debugfs_create_dir(buf, shrinker_debugfs_root); 19562306a36Sopenharmony_ci if (IS_ERR(entry)) { 19662306a36Sopenharmony_ci ida_free(&shrinker_debugfs_ida, id); 19762306a36Sopenharmony_ci return PTR_ERR(entry); 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci shrinker->debugfs_entry = entry; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci debugfs_create_file("count", 0440, entry, shrinker, 20262306a36Sopenharmony_ci &shrinker_debugfs_count_fops); 20362306a36Sopenharmony_ci debugfs_create_file("scan", 0220, entry, shrinker, 20462306a36Sopenharmony_ci &shrinker_debugfs_scan_fops); 20562306a36Sopenharmony_ci return 0; 20662306a36Sopenharmony_ci} 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ciint shrinker_debugfs_rename(struct shrinker *shrinker, const char *fmt, ...) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci struct dentry *entry; 21162306a36Sopenharmony_ci char buf[128]; 21262306a36Sopenharmony_ci const char *new, *old; 21362306a36Sopenharmony_ci va_list ap; 21462306a36Sopenharmony_ci int ret = 0; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci va_start(ap, fmt); 21762306a36Sopenharmony_ci new = kvasprintf_const(GFP_KERNEL, fmt, ap); 21862306a36Sopenharmony_ci va_end(ap); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci if (!new) 22162306a36Sopenharmony_ci return -ENOMEM; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci down_write(&shrinker_rwsem); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci old = shrinker->name; 22662306a36Sopenharmony_ci shrinker->name = new; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci if (shrinker->debugfs_entry) { 22962306a36Sopenharmony_ci snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, 23062306a36Sopenharmony_ci shrinker->debugfs_id); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci entry = debugfs_rename(shrinker_debugfs_root, 23362306a36Sopenharmony_ci shrinker->debugfs_entry, 23462306a36Sopenharmony_ci shrinker_debugfs_root, buf); 23562306a36Sopenharmony_ci if (IS_ERR(entry)) 23662306a36Sopenharmony_ci ret = PTR_ERR(entry); 23762306a36Sopenharmony_ci else 23862306a36Sopenharmony_ci shrinker->debugfs_entry = entry; 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci up_write(&shrinker_rwsem); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci kfree_const(old); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci return ret; 24662306a36Sopenharmony_ci} 24762306a36Sopenharmony_ciEXPORT_SYMBOL(shrinker_debugfs_rename); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_cistruct dentry *shrinker_debugfs_detach(struct shrinker *shrinker, 25062306a36Sopenharmony_ci int *debugfs_id) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci struct dentry *entry = shrinker->debugfs_entry; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci lockdep_assert_held(&shrinker_rwsem); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci kfree_const(shrinker->name); 25762306a36Sopenharmony_ci shrinker->name = NULL; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci *debugfs_id = entry ? shrinker->debugfs_id : -1; 26062306a36Sopenharmony_ci shrinker->debugfs_entry = NULL; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci return entry; 26362306a36Sopenharmony_ci} 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_civoid shrinker_debugfs_remove(struct dentry *debugfs_entry, int debugfs_id) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci debugfs_remove_recursive(debugfs_entry); 26862306a36Sopenharmony_ci ida_free(&shrinker_debugfs_ida, debugfs_id); 26962306a36Sopenharmony_ci} 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_cistatic int __init shrinker_debugfs_init(void) 27262306a36Sopenharmony_ci{ 27362306a36Sopenharmony_ci struct shrinker *shrinker; 27462306a36Sopenharmony_ci struct dentry *dentry; 27562306a36Sopenharmony_ci int ret = 0; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci dentry = debugfs_create_dir("shrinker", NULL); 27862306a36Sopenharmony_ci if (IS_ERR(dentry)) 27962306a36Sopenharmony_ci return PTR_ERR(dentry); 28062306a36Sopenharmony_ci shrinker_debugfs_root = dentry; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci /* Create debugfs entries for shrinkers registered at boot */ 28362306a36Sopenharmony_ci down_write(&shrinker_rwsem); 28462306a36Sopenharmony_ci list_for_each_entry(shrinker, &shrinker_list, list) 28562306a36Sopenharmony_ci if (!shrinker->debugfs_entry) { 28662306a36Sopenharmony_ci ret = shrinker_debugfs_add(shrinker); 28762306a36Sopenharmony_ci if (ret) 28862306a36Sopenharmony_ci break; 28962306a36Sopenharmony_ci } 29062306a36Sopenharmony_ci up_write(&shrinker_rwsem); 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci return ret; 29362306a36Sopenharmony_ci} 29462306a36Sopenharmony_cilate_initcall(shrinker_debugfs_init); 295