2024-03-21 19:36:35 +03:00
// SPDX-License-Identifier: GPL-2.0-only
# include <linux/alloc_tag.h>
# include <linux/fs.h>
# include <linux/gfp.h>
# include <linux/module.h>
2024-03-21 19:36:36 +03:00
# include <linux/page_ext.h>
2024-03-21 19:36:35 +03:00
# include <linux/proc_fs.h>
# include <linux/seq_buf.h>
# include <linux/seq_file.h>
static struct codetag_type * alloc_tag_cttype ;
DEFINE_PER_CPU ( struct alloc_tag_counters , _shared_alloc_tag ) ;
EXPORT_SYMBOL ( _shared_alloc_tag ) ;
DEFINE_STATIC_KEY_MAYBE ( CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT ,
mem_alloc_profiling_key ) ;
2024-05-14 19:31:28 +03:00
struct allocinfo_private {
struct codetag_iterator iter ;
bool print_header ;
} ;
2024-03-21 19:36:35 +03:00
static void * allocinfo_start ( struct seq_file * m , loff_t * pos )
{
2024-05-14 19:31:28 +03:00
struct allocinfo_private * priv ;
2024-03-21 19:36:35 +03:00
struct codetag * ct ;
loff_t node = * pos ;
2024-05-14 19:31:28 +03:00
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
m - > private = priv ;
if ( ! priv )
2024-03-21 19:36:35 +03:00
return NULL ;
2024-05-14 19:31:28 +03:00
priv - > print_header = ( node = = 0 ) ;
2024-03-21 19:36:35 +03:00
codetag_lock_module_list ( alloc_tag_cttype , true ) ;
2024-05-14 19:31:28 +03:00
priv - > iter = codetag_get_ct_iter ( alloc_tag_cttype ) ;
while ( ( ct = codetag_next_ct ( & priv - > iter ) ) ! = NULL & & node )
2024-03-21 19:36:35 +03:00
node - - ;
2024-05-14 19:31:28 +03:00
return ct ? priv : NULL ;
2024-03-21 19:36:35 +03:00
}
static void * allocinfo_next ( struct seq_file * m , void * arg , loff_t * pos )
{
2024-05-14 19:31:28 +03:00
struct allocinfo_private * priv = ( struct allocinfo_private * ) arg ;
struct codetag * ct = codetag_next_ct ( & priv - > iter ) ;
2024-03-21 19:36:35 +03:00
( * pos ) + + ;
if ( ! ct )
return NULL ;
2024-05-14 19:31:28 +03:00
return priv ;
2024-03-21 19:36:35 +03:00
}
static void allocinfo_stop ( struct seq_file * m , void * arg )
{
2024-05-14 19:31:28 +03:00
struct allocinfo_private * priv = ( struct allocinfo_private * ) m - > private ;
2024-03-21 19:36:35 +03:00
2024-05-14 19:31:28 +03:00
if ( priv ) {
2024-03-21 19:36:35 +03:00
codetag_lock_module_list ( alloc_tag_cttype , false ) ;
2024-05-14 19:31:28 +03:00
kfree ( priv ) ;
2024-03-21 19:36:35 +03:00
}
}
2024-05-14 19:31:28 +03:00
static void print_allocinfo_header ( struct seq_buf * buf )
{
/* Output format version, so we can change it. */
seq_buf_printf ( buf , " allocinfo - version: 1.0 \n " ) ;
seq_buf_printf ( buf , " # <size> <calls> <tag info> \n " ) ;
}
2024-03-21 19:36:35 +03:00
static void alloc_tag_to_text ( struct seq_buf * out , struct codetag * ct )
{
struct alloc_tag * tag = ct_to_alloc_tag ( ct ) ;
struct alloc_tag_counters counter = alloc_tag_read ( tag ) ;
s64 bytes = counter . bytes ;
seq_buf_printf ( out , " %12lli %8llu " , bytes , counter . calls ) ;
codetag_to_text ( out , ct ) ;
seq_buf_putc ( out , ' ' ) ;
seq_buf_putc ( out , ' \n ' ) ;
}
static int allocinfo_show ( struct seq_file * m , void * arg )
{
2024-05-14 19:31:28 +03:00
struct allocinfo_private * priv = ( struct allocinfo_private * ) arg ;
2024-03-21 19:36:35 +03:00
char * bufp ;
size_t n = seq_get_buf ( m , & bufp ) ;
struct seq_buf buf ;
seq_buf_init ( & buf , bufp , n ) ;
2024-05-14 19:31:28 +03:00
if ( priv - > print_header ) {
print_allocinfo_header ( & buf ) ;
priv - > print_header = false ;
}
alloc_tag_to_text ( & buf , priv - > iter . ct ) ;
2024-03-21 19:36:35 +03:00
seq_commit ( m , seq_buf_used ( & buf ) ) ;
return 0 ;
}
static const struct seq_operations allocinfo_seq_op = {
. start = allocinfo_start ,
. next = allocinfo_next ,
. stop = allocinfo_stop ,
. show = allocinfo_show ,
} ;
2024-03-21 19:36:54 +03:00
size_t alloc_tag_top_users ( struct codetag_bytes * tags , size_t count , bool can_sleep )
{
struct codetag_iterator iter ;
struct codetag * ct ;
struct codetag_bytes n ;
unsigned int i , nr = 0 ;
if ( can_sleep )
codetag_lock_module_list ( alloc_tag_cttype , true ) ;
else if ( ! codetag_trylock_module_list ( alloc_tag_cttype ) )
return 0 ;
iter = codetag_get_ct_iter ( alloc_tag_cttype ) ;
while ( ( ct = codetag_next_ct ( & iter ) ) ) {
struct alloc_tag_counters counter = alloc_tag_read ( ct_to_alloc_tag ( ct ) ) ;
n . ct = ct ;
n . bytes = counter . bytes ;
for ( i = 0 ; i < nr ; i + + )
if ( n . bytes > tags [ i ] . bytes )
break ;
if ( i < count ) {
nr - = nr = = count ;
memmove ( & tags [ i + 1 ] ,
& tags [ i ] ,
sizeof ( tags [ 0 ] ) * ( nr - i ) ) ;
nr + + ;
tags [ i ] = n ;
}
}
codetag_lock_module_list ( alloc_tag_cttype , false ) ;
return nr ;
}
2024-03-21 19:36:35 +03:00
static void __init procfs_init ( void )
{
2024-04-25 23:08:50 +03:00
proc_create_seq ( " allocinfo " , 0400 , NULL , & allocinfo_seq_op ) ;
2024-03-21 19:36:35 +03:00
}
static bool alloc_tag_module_unload ( struct codetag_type * cttype ,
struct codetag_module * cmod )
{
struct codetag_iterator iter = codetag_get_ct_iter ( cttype ) ;
struct alloc_tag_counters counter ;
bool module_unused = true ;
struct alloc_tag * tag ;
struct codetag * ct ;
for ( ct = codetag_next_ct ( & iter ) ; ct ; ct = codetag_next_ct ( & iter ) ) {
if ( iter . cmod ! = cmod )
continue ;
tag = ct_to_alloc_tag ( ct ) ;
counter = alloc_tag_read ( tag ) ;
if ( WARN ( counter . bytes ,
" %s:%u module %s func:%s has %llu allocated at module unload " ,
ct - > filename , ct - > lineno , ct - > modname , ct - > function , counter . bytes ) )
module_unused = false ;
}
return module_unused ;
}
2024-03-21 19:36:37 +03:00
# ifdef CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT
static bool mem_profiling_support __meminitdata = true ;
# else
static bool mem_profiling_support __meminitdata ;
# endif
static int __init setup_early_mem_profiling ( char * str )
{
bool enable ;
if ( ! str | | ! str [ 0 ] )
return - EINVAL ;
if ( ! strncmp ( str , " never " , 5 ) ) {
enable = false ;
mem_profiling_support = false ;
} else {
int res ;
res = kstrtobool ( str , & enable ) ;
if ( res )
return res ;
mem_profiling_support = true ;
}
if ( enable ! = static_key_enabled ( & mem_alloc_profiling_key ) ) {
if ( enable )
static_branch_enable ( & mem_alloc_profiling_key ) ;
else
static_branch_disable ( & mem_alloc_profiling_key ) ;
}
return 0 ;
}
early_param ( " sysctl.vm.mem_profiling " , setup_early_mem_profiling ) ;
2024-03-21 19:36:36 +03:00
static __init bool need_page_alloc_tagging ( void )
{
2024-03-21 19:36:37 +03:00
return mem_profiling_support ;
2024-03-21 19:36:36 +03:00
}
static __init void init_page_alloc_tagging ( void )
{
}
struct page_ext_operations page_alloc_tagging_ops = {
. size = sizeof ( union codetag_ref ) ,
. need = need_page_alloc_tagging ,
. init = init_page_alloc_tagging ,
} ;
EXPORT_SYMBOL ( page_alloc_tagging_ops ) ;
2024-06-02 02:38:31 +03:00
# ifdef CONFIG_SYSCTL
2024-03-21 19:36:35 +03:00
static struct ctl_table memory_allocation_profiling_sysctls [ ] = {
{
. procname = " mem_profiling " ,
. data = & mem_alloc_profiling_key ,
# ifdef CONFIG_MEM_ALLOC_PROFILING_DEBUG
. mode = 0444 ,
# else
. mode = 0644 ,
# endif
. proc_handler = proc_do_static_key ,
} ,
{ }
} ;
2024-06-02 02:38:31 +03:00
static void __init sysctl_init ( void )
{
if ( ! mem_profiling_support )
memory_allocation_profiling_sysctls [ 0 ] . mode = 0444 ;
register_sysctl_init ( " vm " , memory_allocation_profiling_sysctls ) ;
}
# else /* CONFIG_SYSCTL */
static inline void sysctl_init ( void ) { }
# endif /* CONFIG_SYSCTL */
2024-03-21 19:36:35 +03:00
static int __init alloc_tag_init ( void )
{
const struct codetag_type_desc desc = {
. section = " alloc_tags " ,
. tag_size = sizeof ( struct alloc_tag ) ,
. module_unload = alloc_tag_module_unload ,
} ;
alloc_tag_cttype = codetag_register_type ( & desc ) ;
if ( IS_ERR ( alloc_tag_cttype ) )
return PTR_ERR ( alloc_tag_cttype ) ;
2024-06-02 02:38:31 +03:00
sysctl_init ( ) ;
2024-03-21 19:36:35 +03:00
procfs_init ( ) ;
return 0 ;
}
module_init ( alloc_tag_init ) ;