2022-06-01 06:22:23 +03:00
// 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>
2023-03-13 14:28:16 +03:00
# include <linux/srcu.h>
2022-06-01 06:22:23 +03:00
/* defined in vmscan.c */
2023-03-13 14:28:19 +03:00
extern struct mutex shrinker_mutex ;
2022-06-01 06:22:23 +03:00
extern struct list_head shrinker_list ;
2023-03-13 14:28:16 +03:00
extern struct srcu_struct shrinker_srcu ;
2022-06-01 06:22:23 +03:00
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 ;
2023-03-13 14:28:16 +03:00
int ret = 0 , nid , srcu_idx ;
2022-06-01 06:22:23 +03:00
count_per_node = kcalloc ( nr_node_ids , sizeof ( unsigned long ) , GFP_KERNEL ) ;
if ( ! count_per_node )
return - ENOMEM ;
2023-03-13 14:28:16 +03:00
srcu_idx = srcu_read_lock ( & shrinker_srcu ) ;
2022-06-01 06:22:23 +03:00
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 ) ;
2023-03-13 14:28:16 +03:00
srcu_read_unlock ( & shrinker_srcu , srcu_idx ) ;
2022-06-01 06:22:23 +03:00
kfree ( count_per_node ) ;
return ret ;
}
DEFINE_SHOW_ATTRIBUTE ( shrinker_debugfs_count ) ;
2022-06-01 06:22:27 +03:00
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 ,
} ;
struct mem_cgroup * memcg = NULL ;
2023-03-13 14:28:16 +03:00
int nid , srcu_idx ;
2022-06-01 06:22:27 +03:00
char kbuf [ 72 ] ;
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 , & nid , & nr_to_scan ) ! = 3 )
return - EINVAL ;
if ( nid < 0 | | nid > = nr_node_ids )
return - EINVAL ;
if ( nr_to_scan = = 0 )
return size ;
if ( shrinker - > flags & SHRINKER_MEMCG_AWARE ) {
memcg = mem_cgroup_get_from_ino ( ino ) ;
if ( ! memcg | | IS_ERR ( memcg ) )
return - ENOENT ;
if ( ! mem_cgroup_online ( memcg ) ) {
mem_cgroup_put ( memcg ) ;
return - ENOENT ;
}
} else if ( ino ! = 0 ) {
return - EINVAL ;
}
2023-03-13 14:28:16 +03:00
srcu_idx = srcu_read_lock ( & shrinker_srcu ) ;
2022-06-01 06:22:27 +03:00
sc . nid = nid ;
sc . memcg = memcg ;
sc . nr_to_scan = nr_to_scan ;
sc . nr_scanned = nr_to_scan ;
shrinker - > scan_objects ( shrinker , & sc ) ;
2023-03-13 14:28:16 +03:00
srcu_read_unlock ( & shrinker_srcu , srcu_idx ) ;
2022-06-01 06:22:27 +03:00
mem_cgroup_put ( memcg ) ;
return size ;
}
static const struct file_operations shrinker_debugfs_scan_fops = {
. owner = THIS_MODULE ,
. open = shrinker_debugfs_scan_open ,
. write = shrinker_debugfs_scan_write ,
} ;
2022-06-01 06:22:23 +03:00
int shrinker_debugfs_add ( struct shrinker * shrinker )
{
struct dentry * entry ;
2022-06-01 06:22:24 +03:00
char buf [ 128 ] ;
2022-06-01 06:22:23 +03:00
int id ;
2023-03-13 14:28:19 +03:00
lockdep_assert_held ( & shrinker_mutex ) ;
2022-06-01 06:22:23 +03:00
/* 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 ;
2022-06-01 06:22:24 +03:00
snprintf ( buf , sizeof ( buf ) , " %s-%d " , shrinker - > name , id ) ;
2022-06-01 06:22:23 +03:00
/* 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 ;
2023-04-18 13:19:05 +03:00
debugfs_create_file ( " count " , 0440 , entry , shrinker ,
2022-06-01 06:22:23 +03:00
& shrinker_debugfs_count_fops ) ;
2023-04-18 13:19:05 +03:00
debugfs_create_file ( " scan " , 0220 , entry , shrinker ,
2022-06-01 06:22:27 +03:00
& shrinker_debugfs_scan_fops ) ;
2022-06-01 06:22:23 +03:00
return 0 ;
}
2022-06-01 06:22:24 +03:00
int shrinker_debugfs_rename ( struct shrinker * shrinker , const char * fmt , . . . )
{
struct dentry * entry ;
char buf [ 128 ] ;
const char * new , * old ;
va_list ap ;
int ret = 0 ;
va_start ( ap , fmt ) ;
new = kvasprintf_const ( GFP_KERNEL , fmt , ap ) ;
va_end ( ap ) ;
if ( ! new )
return - ENOMEM ;
2023-03-13 14:28:19 +03:00
mutex_lock ( & shrinker_mutex ) ;
2022-06-01 06:22:24 +03:00
old = shrinker - > name ;
shrinker - > name = new ;
if ( shrinker - > debugfs_entry ) {
snprintf ( buf , sizeof ( buf ) , " %s-%d " , shrinker - > name ,
shrinker - > debugfs_id ) ;
entry = debugfs_rename ( shrinker_debugfs_root ,
shrinker - > debugfs_entry ,
shrinker_debugfs_root , buf ) ;
if ( IS_ERR ( entry ) )
ret = PTR_ERR ( entry ) ;
else
shrinker - > debugfs_entry = entry ;
}
2023-03-13 14:28:19 +03:00
mutex_unlock ( & shrinker_mutex ) ;
2022-06-01 06:22:24 +03:00
kfree_const ( old ) ;
return ret ;
}
EXPORT_SYMBOL ( shrinker_debugfs_rename ) ;
2023-02-02 13:56:12 +03:00
struct dentry * shrinker_debugfs_remove ( struct shrinker * shrinker )
2022-06-01 06:22:23 +03:00
{
2023-02-02 13:56:12 +03:00
struct dentry * entry = shrinker - > debugfs_entry ;
2023-03-13 14:28:19 +03:00
lockdep_assert_held ( & shrinker_mutex ) ;
2022-06-01 06:22:23 +03:00
2022-06-01 06:22:24 +03:00
kfree_const ( shrinker - > name ) ;
2022-07-20 17:47:55 +03:00
shrinker - > name = NULL ;
2022-06-01 06:22:24 +03:00
2023-02-02 13:56:12 +03:00
if ( entry ) {
ida_free ( & shrinker_debugfs_ida , shrinker - > debugfs_id ) ;
shrinker - > debugfs_entry = NULL ;
}
2022-06-01 06:22:23 +03:00
2023-02-02 13:56:12 +03:00
return entry ;
2022-06-01 06:22:23 +03:00
}
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 */
2023-03-13 14:28:19 +03:00
mutex_lock ( & shrinker_mutex ) ;
2022-06-01 06:22:23 +03:00
list_for_each_entry ( shrinker , & shrinker_list , list )
if ( ! shrinker - > debugfs_entry ) {
ret = shrinker_debugfs_add ( shrinker ) ;
if ( ret )
break ;
}
2023-03-13 14:28:19 +03:00
mutex_unlock ( & shrinker_mutex ) ;
2022-06-01 06:22:23 +03:00
return ret ;
}
late_initcall ( shrinker_debugfs_init ) ;