* [PATCH v5 0/3] page_owner: print stacks and their counter
@ 2023-05-16 14:03 Oscar Salvador
2023-05-16 14:03 ` [PATCH v5 1/3] lib/stackdepot: Add a refcount field in stack_record Oscar Salvador
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Oscar Salvador @ 2023-05-16 14:03 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-kernel, linux-mm, Michal Hocko, Vlastimil Babka,
Eric Dumazet, Waiman Long, Suren Baghdasaryan, Marco Elver,
Andrey Konovalov, Alexander Potapenko, Oscar Salvador
Changes v4 -> v5:
- Move page_owner_stack_threshold to page_owner file (suggested
by Alexander Potapenko)
- Make the stack refcount more generic, and do not
assume it will be only used for page_owner (suggested by
Alexander Potapenko)
Changes v3 -> v4:
- Rebase (long time has passed)
- Use boolean instead of enum for action by Alexander Potapenko
- (I left some feedback untouched because it's been long and
would like to discuss it here now instead of re-vamping
and old thread)
Changes v2 -> v3:
- Replace interface in favor of seq operations (suggested by Vlastimil)
- Use debugfs interface to store/read valued (suggested by Ammar)
Hi,
page_owner is a great debug functionality tool that gets us to know
about all pages that have been allocated/freed and their stacktrace.
This comes very handy when e.g: debugging leaks, as with some scripting
we might be able to see those stacktraces that are allocating pages
but not freeing theme.
In my experience, that is one of the most useful cases, but it can get
really tedious to screen through all pages aand try to reconstruct the
stack <-> allocated/freed relationship. There is a lot of noise
to cancel off.
This patch aims to fix that by adding a new functionality into page_owner.
What this does is to create a new read-only file "page_owner_stacks",
which prints only the allocating stacktraces and their counting, being that
the times the stacktrace has allocated - the times it has freed.
So we have a clear overview of stacks <-> allocated/freed relationship
without the need to fiddle with pages and trying to match free stacktraces
with allocated stacktraces.
This is achieved by adding a new refcount_t field in the stack_record struct,
incrementing that refcount_t everytime the same stacktrace allocates,
and decrementing it when it frees a page. Details can be seen in the
respective patches.
We also create another file called "page_owner_threshold", which let us
specify a threshold, so when when reading from "page_owner_stacks",
we will only see those stacktraces which counting goes beyond the
threshold we specified.
One thing I am not completely happy about is to polute lib/stackdepot.c file
with the stack_* functions.
We could sort that out if the stack_record struct definitions were in a header
file instead of stackdepot.c.
But I am not sure about that trade-off, so suggestions are accepted.
A PoC can be found below:
# cat /sys/kernel/debug/page_owner_threshold
0
# cat /sys/kernel/debug/page_owner_stacks > stacks_full.txt
# head -32 stacks_full.txt
prep_new_page+0x10d/0x180
get_page_from_freelist+0x1bd6/0x1e10
__alloc_pages+0x194/0x360
alloc_page_interleave+0x13/0x90
new_slab+0x31d/0x530
___slab_alloc+0x5d7/0x720
__slab_alloc.isra.85+0x4a/0x90
kmem_cache_alloc+0x455/0x4a0
acpi_ps_alloc_op+0x57/0x8f
acpi_ps_create_scope_op+0x12/0x23
acpi_ps_execute_method+0x102/0x2c1
acpi_ns_evaluate+0x343/0x4da
acpi_evaluate_object+0x1cb/0x392
acpi_run_osc+0x135/0x260
acpi_init+0x165/0x4ed
do_one_initcall+0x3e/0x200
stack count: 2
free_pcp_prepare+0x287/0x5c0
free_unref_page+0x1c/0xd0
__mmdrop+0x50/0x160
finish_task_switch+0x249/0x2b0
__schedule+0x2c3/0x960
schedule+0x44/0xb0
futex_wait_queue+0x70/0xd0
futex_wait+0x160/0x250
do_futex+0x11c/0x1b0
__x64_sys_futex+0x5e/0x1d0
do_syscall_64+0x37/0x90
entry_SYSCALL_64_after_hwframe+0x63/0xcd
stack count: 1
# echo 10000 > /sys/kernel/debug/page_owner_threshold
# cat /sys/kernel/debug/page_owner_stacks > stacks_10000.txt
# cat stacks_10000.txt
prep_new_page+0x10d/0x180
get_page_from_freelist+0x1bd6/0x1e10
__alloc_pages+0x194/0x360
folio_alloc+0x17/0x40
page_cache_ra_unbounded+0x96/0x170
filemap_get_pages+0x23d/0x5e0
filemap_read+0xbf/0x3a0
__kernel_read+0x136/0x2f0
kernel_read_file+0x197/0x2d0
kernel_read_file_from_fd+0x54/0x90
__do_sys_finit_module+0x89/0x120
do_syscall_64+0x37/0x90
entry_SYSCALL_64_after_hwframe+0x63/0xcd
stack count: 36195
prep_new_page+0x10d/0x180
get_page_from_freelist+0x1bd6/0x1e10
__alloc_pages+0x194/0x360
folio_alloc+0x17/0x40
page_cache_ra_unbounded+0x96/0x170
filemap_get_pages+0x23d/0x5e0
filemap_read+0xbf/0x3a0
new_sync_read+0x106/0x180
vfs_read+0x16f/0x190
ksys_read+0xa5/0xe0
do_syscall_64+0x37/0x90
entry_SYSCALL_64_after_hwframe+0x63/0xcd
stack count: 44484
prep_new_page+0x10d/0x180
get_page_from_freelist+0x1bd6/0x1e10
__alloc_pages+0x194/0x360
folio_alloc+0x17/0x40
page_cache_ra_unbounded+0x96/0x170
filemap_get_pages+0xdd/0x5e0
filemap_read+0xbf/0x3a0
new_sync_read+0x106/0x180
vfs_read+0x16f/0x190
ksys_read+0xa5/0xe0
do_syscall_64+0x37/0x90
entry_SYSCALL_64_after_hwframe+0x63/0xcd
stack count: 17874
Oscar Salvador (3):
lib/stackdepot: Add a refcount field in stack_record
mm, page_owner: Add page_owner_stacks file to print out only stacks
and their counte
mm,page_owner: Filter out stacks by a threshold counter
include/linux/stackdepot.h | 8 +++
lib/stackdepot.c | 128 ++++++++++++++++++++++++++++++++++---
mm/page_owner.c | 54 ++++++++++++++++
3 files changed, 180 insertions(+), 10 deletions(-)
--
2.35.3
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v5 1/3] lib/stackdepot: Add a refcount field in stack_record 2023-05-16 14:03 [PATCH v5 0/3] page_owner: print stacks and their counter Oscar Salvador @ 2023-05-16 14:03 ` Oscar Salvador 2023-05-16 14:03 ` [PATCH v5 2/3] mm, page_owner: Add page_owner_stacks file to print out only stacks and their counte Oscar Salvador 2023-05-16 14:03 ` [PATCH v5 3/3] mm,page_owner: Filter out stacks by a threshold counter Oscar Salvador 2 siblings, 0 replies; 7+ messages in thread From: Oscar Salvador @ 2023-05-16 14:03 UTC (permalink / raw) To: Andrew Morton Cc: linux-kernel, linux-mm, Michal Hocko, Vlastimil Babka, Eric Dumazet, Waiman Long, Suren Baghdasaryan, Marco Elver, Andrey Konovalov, Alexander Potapenko, Oscar Salvador We want to filter out page_owner output and print only those stacks that have been repeated beyond a certain threshold. This gives us the chance to get rid of a lot of noise. In order to do that, we need to keep track of how many repeated stacks (for allocation) do we have, so we add a new refcount_t field in the stack_record struct. Note that this might increase the size of the struct for some architectures. E.g: x86_64 is not affected due to alignment, but x86 32bits might. The alternative would be to have some kind of struct like this: struct track_stacks { struct stack_record *stack; struct track_stacks *next; refcount_t stack_count; But ithat would imply to perform more allocations and glue everything together, which would make the code more complex, so I think that going with a new field in the struct stack_record is good enough. Note that on __set_page_owner_handle(), page_owner->handle is set, and on __reset_page_owner(), page_owner->free_handle is set. We are interested in page_owner->handle, so when __set_page_owner() gets called, we derive the stack_record struct from page_owner->handle, and we increment its refcount_t field; and when __reset_page_owner() gets called, we derive its stack_record from page_owner->handle() and we decrement its refcount_t field. Signed-off-by: Oscar Salvador <osalvador@suse.de> --- include/linux/stackdepot.h | 2 ++ lib/stackdepot.c | 53 +++++++++++++++++++++++++++++++------- mm/page_owner.c | 6 +++++ 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/include/linux/stackdepot.h b/include/linux/stackdepot.h index e58306783d8e..6ba4fcdb0c5f 100644 --- a/include/linux/stackdepot.h +++ b/include/linux/stackdepot.h @@ -94,6 +94,8 @@ static inline int stack_depot_early_init(void) { return 0; } depot_stack_handle_t __stack_depot_save(unsigned long *entries, unsigned int nr_entries, gfp_t gfp_flags, bool can_alloc); +void stack_depot_inc_count(depot_stack_handle_t handle); +void stack_depot_dec_count(depot_stack_handle_t handle); /** * stack_depot_save - Save a stack trace to stack depot diff --git a/lib/stackdepot.c b/lib/stackdepot.c index 2f5aa851834e..bc4a9cd25834 100644 --- a/lib/stackdepot.c +++ b/lib/stackdepot.c @@ -60,6 +60,7 @@ struct stack_record { u32 hash; /* Hash in the hash table */ u32 size; /* Number of stored frames */ union handle_parts handle; + refcount_t count; /* Number of the same repeated stacks */ unsigned long entries[]; /* Variable-sized array of frames */ }; @@ -305,6 +306,7 @@ depot_alloc_stack(unsigned long *entries, int size, u32 hash, void **prealloc) stack->handle.offset = pool_offset >> DEPOT_STACK_ALIGN; stack->handle.valid = 1; stack->handle.extra = 0; + refcount_set(&stack->count, 1); memcpy(stack->entries, entries, flex_array_size(stack, entries, size)); pool_offset += required_size; /* @@ -457,8 +459,7 @@ depot_stack_handle_t stack_depot_save(unsigned long *entries, } EXPORT_SYMBOL_GPL(stack_depot_save); -unsigned int stack_depot_fetch(depot_stack_handle_t handle, - unsigned long **entries) +static struct stack_record *stack_depot_getstack(depot_stack_handle_t handle) { union handle_parts parts = { .handle = handle }; /* @@ -470,6 +471,26 @@ unsigned int stack_depot_fetch(depot_stack_handle_t handle, size_t offset = parts.offset << DEPOT_STACK_ALIGN; struct stack_record *stack; + if (!handle) + return NULL; + + if (parts.pool_index > pool_index_cached) { + WARN(1, "pool index %d out of bounds (%d) for stack id %08x\n", + parts.pool_index, pool_index_cached, handle); + return NULL; + } + pool = stack_pools[parts.pool_index]; + if (!pool) + return NULL; + stack = pool + offset; + return stack; +} + +unsigned int stack_depot_fetch(depot_stack_handle_t handle, + unsigned long **entries) +{ + struct stack_record *stack; + *entries = NULL; /* * Let KMSAN know *entries is initialized. This shall prevent false @@ -480,21 +501,33 @@ unsigned int stack_depot_fetch(depot_stack_handle_t handle, if (!handle) return 0; - if (parts.pool_index > pool_index_cached) { - WARN(1, "pool index %d out of bounds (%d) for stack id %08x\n", - parts.pool_index, pool_index_cached, handle); - return 0; - } - pool = stack_pools[parts.pool_index]; - if (!pool) + stack = stack_depot_getstack(handle); + if (!stack) return 0; - stack = pool + offset; *entries = stack->entries; return stack->size; } EXPORT_SYMBOL_GPL(stack_depot_fetch); +void stack_depot_inc_count(depot_stack_handle_t handle) +{ + struct stack_record *stack = NULL; + + stack = stack_depot_getstack(handle); + if (stack) + refcount_inc(&stack->count); +} + +void stack_depot_dec_count(depot_stack_handle_t handle) +{ + struct stack_record *stack = NULL; + + stack = stack_depot_getstack(handle); + if (stack) + refcount_dec(&stack->count); +} + void stack_depot_print(depot_stack_handle_t stack) { unsigned long *entries; diff --git a/mm/page_owner.c b/mm/page_owner.c index 31169b3e7f06..2d5d07013e4e 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -139,6 +139,7 @@ void __reset_page_owner(struct page *page, unsigned short order) int i; struct page_ext *page_ext; depot_stack_handle_t handle; + depot_stack_handle_t alloc_handle; struct page_owner *page_owner; u64 free_ts_nsec = local_clock(); @@ -146,6 +147,9 @@ void __reset_page_owner(struct page *page, unsigned short order) if (unlikely(!page_ext)) return; + page_owner = get_page_owner(page_ext); + alloc_handle = page_owner->handle; + handle = save_stack(GFP_NOWAIT | __GFP_NOWARN); for (i = 0; i < (1 << order); i++) { __clear_bit(PAGE_EXT_OWNER_ALLOCATED, &page_ext->flags); @@ -155,6 +159,7 @@ void __reset_page_owner(struct page *page, unsigned short order) page_ext = page_ext_next(page_ext); } page_ext_put(page_ext); + stack_depot_dec_count(alloc_handle); } static inline void __set_page_owner_handle(struct page_ext *page_ext, @@ -196,6 +201,7 @@ noinline void __set_page_owner(struct page *page, unsigned short order, return; __set_page_owner_handle(page_ext, handle, order, gfp_mask); page_ext_put(page_ext); + stack_depot_inc_count(handle); } void __set_page_owner_migrate_reason(struct page *page, int reason) -- 2.35.3 ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v5 2/3] mm, page_owner: Add page_owner_stacks file to print out only stacks and their counte 2023-05-16 14:03 [PATCH v5 0/3] page_owner: print stacks and their counter Oscar Salvador 2023-05-16 14:03 ` [PATCH v5 1/3] lib/stackdepot: Add a refcount field in stack_record Oscar Salvador @ 2023-05-16 14:03 ` Oscar Salvador 2023-05-16 14:03 ` [PATCH v5 3/3] mm,page_owner: Filter out stacks by a threshold counter Oscar Salvador 2 siblings, 0 replies; 7+ messages in thread From: Oscar Salvador @ 2023-05-16 14:03 UTC (permalink / raw) To: Andrew Morton Cc: linux-kernel, linux-mm, Michal Hocko, Vlastimil Babka, Eric Dumazet, Waiman Long, Suren Baghdasaryan, Marco Elver, Andrey Konovalov, Alexander Potapenko, Oscar Salvador We might be only interested in knowing about stacks <-> count relationship, so instead of having to fiddle with page_owner output and screen through pfns, let us add a new file called 'page_owner_stacks' that does just that. By cating such file, we will get all the stacktraces followed by its counter, so we can have a more global view. Signed-off-by: Oscar Salvador <osalvador@suse.de> --- include/linux/stackdepot.h | 6 ++++ lib/stackdepot.c | 72 ++++++++++++++++++++++++++++++++++++++ mm/page_owner.c | 27 ++++++++++++++ 3 files changed, 105 insertions(+) diff --git a/include/linux/stackdepot.h b/include/linux/stackdepot.h index 6ba4fcdb0c5f..7e9d0e9ec66b 100644 --- a/include/linux/stackdepot.h +++ b/include/linux/stackdepot.h @@ -112,6 +112,12 @@ void stack_depot_dec_count(depot_stack_handle_t handle); depot_stack_handle_t stack_depot_save(unsigned long *entries, unsigned int nr_entries, gfp_t gfp_flags); +#ifdef CONFIG_PAGE_OWNER +void *stack_start(struct seq_file *m, loff_t *ppos); +void *stack_next(struct seq_file *m, void *v, loff_t *ppos); +int stack_print(struct seq_file *m, void *v); +#endif + /** * stack_depot_fetch - Fetch a stack trace from stack depot * diff --git a/lib/stackdepot.c b/lib/stackdepot.c index bc4a9cd25834..c4af2e946500 100644 --- a/lib/stackdepot.c +++ b/lib/stackdepot.c @@ -29,6 +29,7 @@ #include <linux/types.h> #include <linux/memblock.h> #include <linux/kasan-enabled.h> +#include <linux/seq_file.h> #define DEPOT_HANDLE_BITS (sizeof(depot_stack_handle_t) * 8) @@ -486,6 +487,77 @@ static struct stack_record *stack_depot_getstack(depot_stack_handle_t handle) return stack; } +#ifdef CONFIG_PAGE_OWNER +void *stack_start(struct seq_file *m, loff_t *ppos) +{ + unsigned long *table = m->private; + struct stack_record **stacks, *stack; + + /* First time */ + if (*ppos == 0) + *table = 0; + + if (*ppos == -1UL) + return NULL; + + stacks = &stack_table[*table]; + stack = (struct stack_record *)stacks; + + return stack; +} + +void *stack_next(struct seq_file *m, void *v, loff_t *ppos) +{ + unsigned long *table = m->private; + unsigned long nr_table = *table; + struct stack_record *next = NULL, *stack = v, **stacks; + unsigned long stack_table_entries = stack_hash_mask + 1; + + if (!stack) { +new_table: + /* New table */ + nr_table++; + if (nr_table >= stack_table_entries) + goto out; + stacks = &stack_table[nr_table]; + stack = (struct stack_record *)stacks; + next = stack; + } else { + next = stack->next; + } + + if (!next) + goto new_table; + +out: + *table = nr_table; + *ppos = (nr_table >= stack_table_entries) ? -1UL : *ppos + 1; + return next; +} + +int stack_print(struct seq_file *m, void *v) +{ + char *buf; + int ret = 0; + struct stack_record *stack = v; + + if (!stack->size || stack->size < 0 || + stack->size > PAGE_SIZE || stack->handle.valid != 1 || + refcount_read(&stack->count) < 1) + return 0; + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + ret += stack_trace_snprint(buf, PAGE_SIZE, stack->entries, stack->size, 0); + scnprintf(buf + ret, PAGE_SIZE - ret, "stack count: %d\n\n", + refcount_read(&stack->count)); + seq_printf(m, buf); + seq_puts(m, "\n\n"); + kfree(buf); + + return 0; +} +#endif + unsigned int stack_depot_fetch(depot_stack_handle_t handle, unsigned long **entries) { diff --git a/mm/page_owner.c b/mm/page_owner.c index 2d5d07013e4e..2d97f6b34ea6 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -719,6 +719,30 @@ static const struct file_operations proc_page_owner_operations = { .llseek = lseek_page_owner, }; +static void stack_stop(struct seq_file *m, void *v) +{ +} + +static const struct seq_operations page_owner_stack_op = { + .start = stack_start, + .next = stack_next, + .stop = stack_stop, + .show = stack_print +}; + +static int page_owner_stack_open(struct inode *inode, struct file *file) +{ + return seq_open_private(file, &page_owner_stack_op, + sizeof(unsigned long)); +} + +const struct file_operations page_owner_stack_operations = { + .open = page_owner_stack_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + static int __init pageowner_init(void) { if (!static_branch_unlikely(&page_owner_inited)) { @@ -729,6 +753,9 @@ static int __init pageowner_init(void) debugfs_create_file("page_owner", 0400, NULL, NULL, &proc_page_owner_operations); + debugfs_create_file("page_owner_stacks", 0400, NULL, NULL, + &page_owner_stack_operations); + return 0; } late_initcall(pageowner_init) -- 2.35.3 ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v5 3/3] mm,page_owner: Filter out stacks by a threshold counter 2023-05-16 14:03 [PATCH v5 0/3] page_owner: print stacks and their counter Oscar Salvador 2023-05-16 14:03 ` [PATCH v5 1/3] lib/stackdepot: Add a refcount field in stack_record Oscar Salvador 2023-05-16 14:03 ` [PATCH v5 2/3] mm, page_owner: Add page_owner_stacks file to print out only stacks and their counte Oscar Salvador @ 2023-05-16 14:03 ` Oscar Salvador 2 siblings, 0 replies; 7+ messages in thread From: Oscar Salvador @ 2023-05-16 14:03 UTC (permalink / raw) To: Andrew Morton Cc: linux-kernel, linux-mm, Michal Hocko, Vlastimil Babka, Eric Dumazet, Waiman Long, Suren Baghdasaryan, Marco Elver, Andrey Konovalov, Alexander Potapenko, Oscar Salvador We want to be able to filter out the output on a threshold basis, in this way we can get rid of a lot of noise and focus only on those stacks which have an allegedly high counter. We can control the threshold value by a new file called 'page_owner_threshold', which is 0 by default. Signed-off-by: Oscar Salvador <osalvador@suse.de> --- lib/stackdepot.c | 5 ++++- mm/page_owner.c | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/stackdepot.c b/lib/stackdepot.c index c4af2e946500..7053221ce1d8 100644 --- a/lib/stackdepot.c +++ b/lib/stackdepot.c @@ -488,6 +488,9 @@ static struct stack_record *stack_depot_getstack(depot_stack_handle_t handle) } #ifdef CONFIG_PAGE_OWNER + +extern unsigned long page_owner_stack_threshold; + void *stack_start(struct seq_file *m, loff_t *ppos) { unsigned long *table = m->private; @@ -543,7 +546,7 @@ int stack_print(struct seq_file *m, void *v) if (!stack->size || stack->size < 0 || stack->size > PAGE_SIZE || stack->handle.valid != 1 || - refcount_read(&stack->count) < 1) + refcount_read(&stack->count) < page_owner_stack_threshold) return 0; buf = kzalloc(PAGE_SIZE, GFP_KERNEL); diff --git a/mm/page_owner.c b/mm/page_owner.c index 2d97f6b34ea6..28c519fc9372 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -743,6 +743,23 @@ const struct file_operations page_owner_stack_operations = { .release = seq_release, }; +unsigned long page_owner_stack_threshold; + +int page_owner_threshold_get(void *data, u64 *val) +{ + *val = page_owner_stack_threshold; + return 0; +} + +int page_owner_threshold_set(void *data, u64 val) +{ + page_owner_stack_threshold = val; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(proc_page_owner_threshold, &page_owner_threshold_get, + &page_owner_threshold_set, "%llu"); + static int __init pageowner_init(void) { if (!static_branch_unlikely(&page_owner_inited)) { @@ -755,6 +772,10 @@ static int __init pageowner_init(void) debugfs_create_file("page_owner_stacks", 0400, NULL, NULL, &page_owner_stack_operations); + debugfs_create_file("page_owner_threshold", 0600, NULL, NULL, + &proc_page_owner_threshold); + + page_owner_stack_threshold = 0; return 0; } -- 2.35.3 ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v5 0/3] page_owner: print stacks and their counter
@ 2023-05-16 18:25 Oscar Salvador
2023-05-16 18:25 ` [PATCH v5 3/3] mm,page_owner: Filter out stacks by a threshold counter Oscar Salvador
0 siblings, 1 reply; 7+ messages in thread
From: Oscar Salvador @ 2023-05-16 18:25 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-kernel, linux-mm, Michal Hocko, Vlastimil Babka,
Waiman Long, Suren Baghdasaryan, Marco Elver, Andrey Konovalov,
Eric Dumazet, Alexander Potapenko, Oscar Salvador
Changes v4 -> v5:
- Move page_owner_stack_threshold to page_owner file (suggested
by Alexander Potapenko)
- Make the stack refcount more generic, and do not
assume it will be only used for page_owner (suggested by
Alexander Potapenko)
Changes v3 -> v4:
- Rebase (long time has passed)
- Use boolean instead of enum for action by Alexander Potapenko
- (I left some feedback untouched because it's been long and
would like to discuss it here now instead of re-vamping
and old thread)
Changes v2 -> v3:
- Replace interface in favor of seq operations (suggested by Vlastimil)
- Use debugfs interface to store/read valued (suggested by Ammar)
Hi,
page_owner is a great debug functionality tool that gets us to know
about all pages that have been allocated/freed and their stacktrace.
This comes very handy when e.g: debugging leaks, as with some scripting
we might be able to see those stacktraces that are allocating pages
but not freeing theme.
In my experience, that is one of the most useful cases, but it can get
really tedious to screen through all pages aand try to reconstruct the
stack <-> allocated/freed relationship. There is a lot of noise
to cancel off.
This patch aims to fix that by adding a new functionality into page_owner.
What this does is to create a new read-only file "page_owner_stacks",
which prints only the allocating stacktraces and their counting, being that
the times the stacktrace has allocated - the times it has freed.
So we have a clear overview of stacks <-> allocated/freed relationship
without the need to fiddle with pages and trying to match free stacktraces
with allocated stacktraces.
This is achieved by adding a new refcount_t field in the stack_record struct,
incrementing that refcount_t everytime the same stacktrace allocates,
and decrementing it when it frees a page. Details can be seen in the
respective patches.
We also create another file called "page_owner_threshold", which let us
specify a threshold, so when when reading from "page_owner_stacks",
we will only see those stacktraces which counting goes beyond the
threshold we specified.
One thing I am not completely happy about is to polute lib/stackdepot.c file
with the stack_* functions.
We could sort that out if the stack_record struct definitions were in a header
file instead of stackdepot.c.
But I am not sure about that trade-off, so suggestions are accepted.
A PoC can be found below:
# cat /sys/kernel/debug/page_owner_threshold
0
# cat /sys/kernel/debug/page_owner_stacks > stacks_full.txt
# head -32 stacks_full.txt
prep_new_page+0x10d/0x180
get_page_from_freelist+0x1bd6/0x1e10
__alloc_pages+0x194/0x360
alloc_page_interleave+0x13/0x90
new_slab+0x31d/0x530
___slab_alloc+0x5d7/0x720
__slab_alloc.isra.85+0x4a/0x90
kmem_cache_alloc+0x455/0x4a0
acpi_ps_alloc_op+0x57/0x8f
acpi_ps_create_scope_op+0x12/0x23
acpi_ps_execute_method+0x102/0x2c1
acpi_ns_evaluate+0x343/0x4da
acpi_evaluate_object+0x1cb/0x392
acpi_run_osc+0x135/0x260
acpi_init+0x165/0x4ed
do_one_initcall+0x3e/0x200
stack count: 2
free_pcp_prepare+0x287/0x5c0
free_unref_page+0x1c/0xd0
__mmdrop+0x50/0x160
finish_task_switch+0x249/0x2b0
__schedule+0x2c3/0x960
schedule+0x44/0xb0
futex_wait_queue+0x70/0xd0
futex_wait+0x160/0x250
do_futex+0x11c/0x1b0
__x64_sys_futex+0x5e/0x1d0
do_syscall_64+0x37/0x90
entry_SYSCALL_64_after_hwframe+0x63/0xcd
stack count: 1
# echo 10000 > /sys/kernel/debug/page_owner_threshold
# cat /sys/kernel/debug/page_owner_stacks > stacks_10000.txt
# cat stacks_10000.txt
prep_new_page+0x10d/0x180
get_page_from_freelist+0x1bd6/0x1e10
__alloc_pages+0x194/0x360
folio_alloc+0x17/0x40
page_cache_ra_unbounded+0x96/0x170
filemap_get_pages+0x23d/0x5e0
filemap_read+0xbf/0x3a0
__kernel_read+0x136/0x2f0
kernel_read_file+0x197/0x2d0
kernel_read_file_from_fd+0x54/0x90
__do_sys_finit_module+0x89/0x120
do_syscall_64+0x37/0x90
entry_SYSCALL_64_after_hwframe+0x63/0xcd
stack count: 36195
prep_new_page+0x10d/0x180
get_page_from_freelist+0x1bd6/0x1e10
__alloc_pages+0x194/0x360
folio_alloc+0x17/0x40
page_cache_ra_unbounded+0x96/0x170
filemap_get_pages+0x23d/0x5e0
filemap_read+0xbf/0x3a0
new_sync_read+0x106/0x180
vfs_read+0x16f/0x190
ksys_read+0xa5/0xe0
do_syscall_64+0x37/0x90
entry_SYSCALL_64_after_hwframe+0x63/0xcd
stack count: 44484
prep_new_page+0x10d/0x180
get_page_from_freelist+0x1bd6/0x1e10
__alloc_pages+0x194/0x360
folio_alloc+0x17/0x40
page_cache_ra_unbounded+0x96/0x170
filemap_get_pages+0xdd/0x5e0
filemap_read+0xbf/0x3a0
new_sync_read+0x106/0x180
vfs_read+0x16f/0x190
ksys_read+0xa5/0xe0
do_syscall_64+0x37/0x90
entry_SYSCALL_64_after_hwframe+0x63/0xcd
stack count: 17874
Oscar Salvador (3):
lib/stackdepot: Add a refcount field in stack_record
mm, page_owner: Add page_owner_stacks file to print out only stacks
and their counte
mm,page_owner: Filter out stacks by a threshold counter
include/linux/stackdepot.h | 8 +++
lib/stackdepot.c | 128 ++++++++++++++++++++++++++++++++++---
mm/page_owner.c | 54 ++++++++++++++++
3 files changed, 180 insertions(+), 10 deletions(-)
--
2.35.3
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v5 3/3] mm,page_owner: Filter out stacks by a threshold counter 2023-05-16 18:25 [PATCH v5 0/3] page_owner: print stacks and their counter Oscar Salvador @ 2023-05-16 18:25 ` Oscar Salvador 2023-05-22 8:40 ` Alexander Potapenko 0 siblings, 1 reply; 7+ messages in thread From: Oscar Salvador @ 2023-05-16 18:25 UTC (permalink / raw) To: Andrew Morton Cc: linux-kernel, linux-mm, Michal Hocko, Vlastimil Babka, Waiman Long, Suren Baghdasaryan, Marco Elver, Andrey Konovalov, Eric Dumazet, Alexander Potapenko, Oscar Salvador We want to be able to filter out the output on a threshold basis, in this way we can get rid of a lot of noise and focus only on those stacks which have an allegedly high counter. We can control the threshold value by a new file called 'page_owner_threshold', which is 0 by default. Signed-off-by: Oscar Salvador <osalvador@suse.de> --- lib/stackdepot.c | 5 ++++- mm/page_owner.c | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/stackdepot.c b/lib/stackdepot.c index c4af2e946500..7053221ce1d8 100644 --- a/lib/stackdepot.c +++ b/lib/stackdepot.c @@ -488,6 +488,9 @@ static struct stack_record *stack_depot_getstack(depot_stack_handle_t handle) } #ifdef CONFIG_PAGE_OWNER + +extern unsigned long page_owner_stack_threshold; + void *stack_start(struct seq_file *m, loff_t *ppos) { unsigned long *table = m->private; @@ -543,7 +546,7 @@ int stack_print(struct seq_file *m, void *v) if (!stack->size || stack->size < 0 || stack->size > PAGE_SIZE || stack->handle.valid != 1 || - refcount_read(&stack->count) < 1) + refcount_read(&stack->count) < page_owner_stack_threshold) return 0; buf = kzalloc(PAGE_SIZE, GFP_KERNEL); diff --git a/mm/page_owner.c b/mm/page_owner.c index 2d97f6b34ea6..28c519fc9372 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -743,6 +743,23 @@ const struct file_operations page_owner_stack_operations = { .release = seq_release, }; +unsigned long page_owner_stack_threshold; + +int page_owner_threshold_get(void *data, u64 *val) +{ + *val = page_owner_stack_threshold; + return 0; +} + +int page_owner_threshold_set(void *data, u64 val) +{ + page_owner_stack_threshold = val; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(proc_page_owner_threshold, &page_owner_threshold_get, + &page_owner_threshold_set, "%llu"); + static int __init pageowner_init(void) { if (!static_branch_unlikely(&page_owner_inited)) { @@ -755,6 +772,10 @@ static int __init pageowner_init(void) debugfs_create_file("page_owner_stacks", 0400, NULL, NULL, &page_owner_stack_operations); + debugfs_create_file("page_owner_threshold", 0600, NULL, NULL, + &proc_page_owner_threshold); + + page_owner_stack_threshold = 0; return 0; } -- 2.35.3 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v5 3/3] mm,page_owner: Filter out stacks by a threshold counter 2023-05-16 18:25 ` [PATCH v5 3/3] mm,page_owner: Filter out stacks by a threshold counter Oscar Salvador @ 2023-05-22 8:40 ` Alexander Potapenko 2023-06-12 10:53 ` Vlastimil Babka 0 siblings, 1 reply; 7+ messages in thread From: Alexander Potapenko @ 2023-05-22 8:40 UTC (permalink / raw) To: Oscar Salvador Cc: Andrew Morton, linux-kernel, linux-mm, Michal Hocko, Vlastimil Babka, Waiman Long, Suren Baghdasaryan, Marco Elver, Andrey Konovalov, Eric Dumazet On Tue, May 16, 2023 at 8:25 PM Oscar Salvador <osalvador@suse.de> wrote: > > We want to be able to filter out the output on a threshold basis, > in this way we can get rid of a lot of noise and focus only on those > stacks which have an allegedly high counter. > > We can control the threshold value by a new file called > 'page_owner_threshold', which is 0 by default. > > Signed-off-by: Oscar Salvador <osalvador@suse.de> Nack. We must decouple stackdepot from page_owner as much as possible, not add extra dependencies by sharing variables between the two. What you need here is a pair of xxx_iter_start()/xxx_iter_next() functions in stackdepot.c that will hold the iteration state in a struct and return the next stack trace. The threshold value can also be part of that struct. ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v5 3/3] mm,page_owner: Filter out stacks by a threshold counter 2023-05-22 8:40 ` Alexander Potapenko @ 2023-06-12 10:53 ` Vlastimil Babka 0 siblings, 0 replies; 7+ messages in thread From: Vlastimil Babka @ 2023-06-12 10:53 UTC (permalink / raw) To: Alexander Potapenko, Oscar Salvador Cc: Andrew Morton, linux-kernel, linux-mm, Michal Hocko, Waiman Long, Suren Baghdasaryan, Marco Elver, Andrey Konovalov, Eric Dumazet On 5/22/23 10:40, Alexander Potapenko wrote: > On Tue, May 16, 2023 at 8:25 PM Oscar Salvador <osalvador@suse.de> wrote: >> We want to be able to filter out the output on a threshold basis, >> in this way we can get rid of a lot of noise and focus only on those >> stacks which have an allegedly high counter. >> >> We can control the threshold value by a new file called >> 'page_owner_threshold', which is 0 by default. >> >> Signed-off-by: Oscar Salvador <osalvador@suse.de> > Nack. > We must decouple stackdepot from page_owner as much as possible, not > add extra dependencies by sharing variables between the two. > > What you need here is a pair of xxx_iter_start()/xxx_iter_next() > functions in stackdepot.c that will hold the iteration state in a > struct and return the next stack trace. > The threshold value can also be part of that struct. Agreed, that should be a much cleaner approach. ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2023-06-12 11:06 UTC | newest] Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2023-05-16 14:03 [PATCH v5 0/3] page_owner: print stacks and their counter Oscar Salvador 2023-05-16 14:03 ` [PATCH v5 1/3] lib/stackdepot: Add a refcount field in stack_record Oscar Salvador 2023-05-16 14:03 ` [PATCH v5 2/3] mm, page_owner: Add page_owner_stacks file to print out only stacks and their counte Oscar Salvador 2023-05-16 14:03 ` [PATCH v5 3/3] mm,page_owner: Filter out stacks by a threshold counter Oscar Salvador 2023-05-16 18:25 [PATCH v5 0/3] page_owner: print stacks and their counter Oscar Salvador 2023-05-16 18:25 ` [PATCH v5 3/3] mm,page_owner: Filter out stacks by a threshold counter Oscar Salvador 2023-05-22 8:40 ` Alexander Potapenko 2023-06-12 10:53 ` Vlastimil Babka
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®