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@suse.de>
---
include/linux/stackdepot.h | 2 +
lib/stackdepot.c | 29 +++++++++++++
mm/page_owner.c | 87 ++++++++++++++++++++++++++++++++++++++
3 files changed, 118 insertions(+)
@@ -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
*
@@ -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)
{
@@ -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;
}