From 225ef8c44e6abb639352ea6b2281a0367dd9cdb3 Mon Sep 17 00:00:00 2001 From: Mauricio Faria de Oliveira Date: Wed, 1 Oct 2025 14:56:07 -0300 Subject: [PATCH] mm/page_owner: introduce struct stack_print_ctx Patch series "mm/page_owner: add debugfs files 'show_handles' and 'show_stacks_handles'", v2. Context: The page_owner debug feature can help understand a particular situation in in a point in time (e.g., identify biggest memory consumers; verify memory counters that do not add up). Another useful usecase is to collect data repeatedly over time, and use it for profiling, monitoring, and even comparing different kernel versions, at the stack trace level (e.g., watch for trends, leaks, correlations, and regressions). For this usecase, userspace periorically collects the data from page_owner and organizes it in data structures appropriate for access per-stack trace. Problem: The usecase of tracking memory usage per stack trace (or tracking it for a particular stack trace) requires uniquely identifying each stack trace (i.e., keys to store their memory usage over periodic data collections). This has to be done for every stack trace in every sample/data collection, even if tracking only one stack trace (to identify it among all others). Therefore, an approach like hashing the stack traces in userspace to create unique keys/identifiers for them during post-processing can quickly become expensive, considering the repetition and a growing number of stack traces. Solution: Fortunately, the kernel can provide a unique identifier for stack traces in page_owner, which is the handle number in stackdepot. This eliminates the need for creating keys (hashing) in userspace during post-processing. Additionally, with that information, the stack traces themselves are not needed until the memory usage should be resolved from a handle to a stack trace (say, to look at the stack traces of a few top consumers). This can reduce the amount of text emitted/copied by the kernel to userspace, and save userspace from matching and discarding stack traces when not needed. Changes: This patchset adds 2 files to provide information, like 'show_stacks': - show_handles: print handle number and number of pages (no stack traces) - show_stacks_handles: print handle numbers and stack traces (no pages) Now, it's possible to periodically collect data with handle numbers (keys) and without stack traces (lower overhead) from 'show_handles', and later do a final collection with handles and stack traces from 'show_stacks_handles' to resolve the handles to their stack traces. The output format follows the existing 'show_stacks' file, for simplicity, but it can certainly be changed if a different format is more convenient. Example: The number of base pages collected can be stored per-handle number over the periodic data collections, and finally resolved to stack traces per-handle number as well with a final collection. Later, one can, for example, identify the biggest consumers and watch their trends or correlate increases/decreases with other events in the system, or watch a particular stack trace(s) of interest during development. Testing: Tested on next-20250929. - show_stacks: register_dummy_stack+0x32/0x70 init_page_owner+0x29/0x2f0 page_ext_init+0x27c/0x2b0 mm_core_init+0xdc/0x110 nr_base_pages: 47 - show_handles: handle: 1 nr_base_pages: 47 - show_stacks_handles: register_dummy_stack+0x32/0x70 init_page_owner+0x29/0x2f0 page_ext_init+0x27c/0x2b0 mm_core_init+0xdc/0x110 handle: 1 - count_threshold: # echo 100 >/sys/kernel/debug/page_owner_stacks/count_threshold # grep register_dummy_stack show_stacks # not present # grep -B4 '^handle: 1$' show_handles # not present # grep -B4 '^handle: 1$' show_stacks_handles # present register_dummy_stack+0x32/0x70 init_page_owner+0x29/0x2f0 page_ext_init+0x27c/0x2b0 mm_core_init+0xdc/0x110 handle: 1 This patch (of 5): Currently, struct seq_file.private is used as an iterator in stack_list by stack_start|next(), for stack_print(). Create a context struct for this, in order to add another field next. No behavior change intended. P.S.: page_owner_stack_open() is expanded with separate statements for variable definition and return just in preparation for the next patch. Link: https://lkml.kernel.org/r/20251001175611.575861-1-mfo@igalia.com Link: https://lkml.kernel.org/r/20251001175611.575861-2-mfo@igalia.com Signed-off-by: Mauricio Faria de Oliveira Reviewed-by: Oscar Salvador Cc: Brendan Jackman Cc: Johannes Weiner Cc: Michal Hocko Cc: Oscar Salvador Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/page_owner.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index 589ec37c94aa..05e26c9d43ef 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -45,6 +45,10 @@ static struct stack failure_stack; static struct stack *stack_list; static DEFINE_SPINLOCK(stack_list_lock); +struct stack_print_ctx { + struct stack *stack; +}; + static bool page_owner_enabled __initdata; DEFINE_STATIC_KEY_FALSE(page_owner_inited); @@ -859,6 +863,7 @@ static const struct file_operations proc_page_owner_operations = { static void *stack_start(struct seq_file *m, loff_t *ppos) { struct stack *stack; + struct stack_print_ctx *ctx = m->private; if (*ppos == -1UL) return NULL; @@ -870,9 +875,9 @@ static void *stack_start(struct seq_file *m, loff_t *ppos) * value of stack_list. */ stack = smp_load_acquire(&stack_list); - m->private = stack; + ctx->stack = stack; } else { - stack = m->private; + stack = ctx->stack; } return stack; @@ -881,10 +886,11 @@ static void *stack_start(struct seq_file *m, loff_t *ppos) static void *stack_next(struct seq_file *m, void *v, loff_t *ppos) { struct stack *stack = v; + struct stack_print_ctx *ctx = m->private; stack = stack->next; *ppos = stack ? *ppos + 1 : -1UL; - m->private = stack; + ctx->stack = stack; return stack; } @@ -929,7 +935,10 @@ static const struct seq_operations page_owner_stack_op = { static int page_owner_stack_open(struct inode *inode, struct file *file) { - return seq_open_private(file, &page_owner_stack_op, 0); + int ret = seq_open_private(file, &page_owner_stack_op, + sizeof(struct stack_print_ctx)); + + return ret; } static const struct file_operations page_owner_stack_operations = { -- 2.51.0