[PATCH v6 3/4] mm,page_owner: Add page_owner_stacks file to print out only stacks and their counter

From: Oscar Salvador
Date: Mon Nov 20 2023 - 03:43:32 EST


We are not interested in the owner of each individual pfn,
but how many outstanding allocations are there for each unique
allocating stack trace.

Right now, getting that information is quite hard as one needs
to fiddle with page_owner output, screen through pfns and make
the links.

So, instead, let us add a new file called 'page_owner_stacks'
that shows just that.
Such file will only show the stacktrace once followed by its
counter, which represents the number of outstanding allocations.

Signed-off-by: Oscar Salvador <osalvador@xxxxxxx>
---
include/linux/stackdepot.h | 2 +
lib/stackdepot.c | 29 +++++++++++++
mm/page_owner.c | 87 ++++++++++++++++++++++++++++++++++++++
3 files changed, 118 insertions(+)

diff --git a/include/linux/stackdepot.h b/include/linux/stackdepot.h
index 269a828a5e94..09c478b1bf73 100644
--- a/include/linux/stackdepot.h
+++ b/include/linux/stackdepot.h
@@ -146,6 +146,8 @@ 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);

+struct stack_record *stack_depot_get_next_stack(unsigned long *table,
+ struct stack_record *curr_stack);
/**
* stack_depot_fetch - Fetch a stack trace from stack depot
*
diff --git a/lib/stackdepot.c b/lib/stackdepot.c
index 1343d3095bc1..65708c0c1e50 100644
--- a/lib/stackdepot.c
+++ b/lib/stackdepot.c
@@ -452,6 +452,35 @@ static struct stack_record *stack_depot_getstack(depot_stack_handle_t handle)
return stack;
}

+struct stack_record *stack_depot_get_next_stack(unsigned long *table,
+ struct stack_record *curr_stack)
+{
+ unsigned long nr_table = *table;
+ struct stack_record *next = NULL, **stacks;
+ unsigned long stack_table_entries = stack_hash_mask + 1;
+
+ if (!curr_stack) {
+ if (nr_table) {
+new_table:
+ nr_table++;
+ if (nr_table >= stack_table_entries)
+ goto out;
+ }
+ stacks = &stack_table[nr_table];
+ curr_stack = (struct stack_record *)stacks;
+ next = curr_stack;
+ } else {
+ next = curr_stack->next;
+ }
+
+ if (!next)
+ goto new_table;
+
+out:
+ *table = nr_table;
+ return next;
+}
+
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 d53316d0d9be..509c11e506db 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -719,6 +719,91 @@ static const struct file_operations proc_page_owner_operations = {
.llseek = lseek_page_owner,
};

+static void *stack_start(struct seq_file *m, loff_t *ppos)
+{
+ unsigned long *nr_table = m->private;
+ void *stack;
+
+ /* First time */
+ if (*ppos == 0)
+ *nr_table = 0;
+
+ if (*ppos == -1UL)
+ return NULL;
+
+ stack = stack_depot_get_next_stack(nr_table, NULL);
+
+ return stack;
+}
+
+static void *stack_next(struct seq_file *m, void *v, loff_t *ppos)
+{
+ unsigned long *nr_table = m->private;
+ void *next_stack;
+
+ next_stack = stack_depot_get_next_stack(nr_table, v);
+ *ppos = next_stack ? *ppos + 1 : -1UL;
+
+ return next_stack;
+}
+
+static int stack_depot_get_stack_info(struct stack_record *stack, char *buf)
+{
+ if (!stack->size || stack->size < 0 ||
+ stack->size > PAGE_SIZE || stack->handle.valid != 1 ||
+ refcount_read(&stack->count) < 1)
+ return 0;
+
+ return stack_trace_snprint(buf, PAGE_SIZE, stack->entries, stack->size, 0);
+}
+
+static int stack_print(struct seq_file *m, void *v)
+{
+ char *buf;
+ int ret = 0;
+ struct stack_record *stack = (struct stack_record *)v;
+
+ buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+
+ ret += stack_depot_get_stack_info(stack, buf);
+ if (!ret)
+ goto out;
+
+ scnprintf(buf + ret, PAGE_SIZE - ret, "stack_count: %d\n\n",
+ refcount_read(&stack->count));
+
+ seq_printf(m, buf);
+ seq_puts(m, "\n\n");
+out:
+ kfree(buf);
+
+ return 0;
+}
+
+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)) {
@@ -728,6 +813,8 @@ 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;
}
--
2.42.0