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>
/* 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 ) ;
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 ;
int nid ;
char kbuf [ 72 ] ;
ssize_t ret ;
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 ;
}
ret = down_read_killable ( & shrinker_rwsem ) ;
if ( ret ) {
mem_cgroup_put ( memcg ) ;
return ret ;
}
sc . nid = nid ;
sc . memcg = memcg ;
sc . nr_to_scan = nr_to_scan ;
sc . nr_scanned = nr_to_scan ;
shrinker - > scan_objects ( shrinker , & sc ) ;
up_read ( & shrinker_rwsem ) ;
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 ;
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 ;
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 ;
debugfs_create_file ( " count " , 0220 , entry , shrinker ,
& shrinker_debugfs_count_fops ) ;
2022-06-01 06:22:27 +03:00
debugfs_create_file ( " scan " , 0440 , entry , shrinker ,
& 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 ;
down_write ( & shrinker_rwsem ) ;
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 ;
}
up_write ( & shrinker_rwsem ) ;
kfree_const ( old ) ;
return ret ;
}
EXPORT_SYMBOL ( shrinker_debugfs_rename ) ;
2022-06-01 06:22:23 +03:00
void shrinker_debugfs_remove ( struct shrinker * shrinker )
{
lockdep_assert_held ( & shrinker_rwsem ) ;
2022-06-01 06:22:24 +03:00
kfree_const ( shrinker - > name ) ;
2022-06-01 06:22:23 +03:00
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 ) ;