diff mbox series

[RFC,3/3] mm/slub: add all_objects implementation in debugfs

Message ID 20210521121127.24653-3-glittao@gmail.com (mailing list archive)
State New, archived
Headers show
Series [RFC,1/3] mm/slub: aggregate objects in cache by stack trace | expand

Commit Message

Oliver Glitta May 21, 2021, 12:11 p.m. UTC
From: Oliver Glitta <glittao@gmail.com>

Add all_objects implementation to debugfs to print information
about all objects in slub cache.

Signed-off-by: Oliver Glitta <glittao@gmail.com>
---
 mm/slub.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 225 insertions(+)

Comments

Vlastimil Babka May 26, 2021, 2:26 p.m. UTC | #1
On 5/21/21 2:11 PM, glittao@gmail.com wrote:
> From: Oliver Glitta <glittao@gmail.com>
> 
> Add all_objects implementation to debugfs to print information
> about all objects in slub cache.

An example listing of 1-2 objects would be useful in the changelog.

Also can you describe what are the guarantees (or limitations) of observing
really all objects if the cache is modified by concurrent allocation and free
operations?

...

> +static void *debugfs_all_objects_start(struct seq_file *m, loff_t *ppos)
> +{
> +	struct slab_debug_private *priv = m->private;
> +	struct kmem_cache *s = priv->inode->i_private;
> +	struct page *page;
> +
> +	priv->map = kmalloc(BITS_TO_LONGS(MAX_OBJS_PER_PAGE), GFP_KERNEL);

We can use bitmap_alloc/bitmap_free wrappers and allocate according to objects
per page in the actual kmem_cache, not the theoretical maximum, see:
https://lore.kernel.org/linux-mm/20210524233946.20352-2-vbabka@suse.cz/
diff mbox series

Patch

diff --git a/mm/slub.c b/mm/slub.c
index 247983d647cd..885d0b074e31 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -4789,6 +4789,17 @@  struct loc_track {
 	struct location *loc;
 };
 
+enum slub_list_field { PARTIAL_LIST, FULL_LIST };
+
+struct slab_debug_private {
+	struct inode *inode;
+	struct kmem_cache_node *node;
+	unsigned long nid;
+	long slabs_remaining;
+	enum slub_list_field field;
+	unsigned long *map;
+};
+
 static struct dentry *slab_debugfs_root;
 struct loc_track t = { 0, 0, NULL };
 
@@ -5809,6 +5820,216 @@  static int debugfs_slab_alias(struct kmem_cache *s, const char *name)
 	return 0;
 }
 
+static struct kmem_cache_node *find_node(struct kmem_cache *s, unsigned long *nid)
+{
+	struct kmem_cache_node *node = NULL;
+
+	while (*nid < nr_node_ids) {
+		node = s->node[*nid];
+		++*nid;
+		if (!node || !atomic_long_read(&node->nr_slabs))
+			node = NULL;
+		else
+			break;
+	}
+	return node;
+}
+
+static bool next_page_new_node(struct slab_debug_private *priv)
+{
+	struct kmem_cache_node *node;
+	struct kmem_cache *s = priv->inode->i_private;
+
+	node = find_node(s, &priv->nid);
+
+	if (!node)
+		return false;
+
+	priv->node = node;
+
+	if (node->nr_partial > 0) {
+		priv->field = PARTIAL_LIST;
+		priv->slabs_remaining = node->nr_partial;
+	} else if (atomic_long_read(&node->nr_slabs) > 0) {
+		priv->field = FULL_LIST;
+		priv->slabs_remaining = atomic_long_read(&node->nr_slabs);
+	}
+
+	return priv->slabs_remaining;
+}
+
+static struct page *next_page(struct slab_debug_private *priv)
+{
+	struct page *page = NULL;
+	struct kmem_cache *s = priv->inode->i_private;
+	struct kmem_cache_node *node;
+	unsigned long flags;
+
+redo:
+	node = priv->node;
+	if (priv->slabs_remaining > 0) {
+		struct list_head *head;
+		void *p, *addr;
+
+		--priv->slabs_remaining;
+
+		if (priv->field == PARTIAL_LIST)
+			head = &node->partial;
+		else
+			head = &node->full;
+
+		spin_lock_irqsave(&node->list_lock, flags);
+		page = list_first_entry(head, struct page, slab_list);
+		if (page) {
+			get_page(page);
+			slab_lock(page);
+			addr = page_address(page);
+			bitmap_zero(priv->map, page->objects);
+
+			for (p = page->freelist; p; p = get_freepointer(s, p))
+				set_bit(__obj_to_index(s, addr, p), priv->map);
+			slab_unlock(page);
+		}
+		list_rotate_left(head);
+		spin_unlock_irqrestore(&node->list_lock, flags);
+
+	} else if ((priv->field == PARTIAL_LIST)
+			&& (atomic_long_read(&node->nr_slabs) != node->nr_partial)) {
+
+		priv->field = FULL_LIST;
+		priv->slabs_remaining = atomic_long_read(&node->nr_slabs) - node->nr_partial;
+
+		goto redo;
+	} else {
+		if (next_page_new_node(priv))
+			goto redo;
+	}
+
+	return page;
+}
+
+static int debugfs_all_objects_show(struct seq_file *seq, void *v)
+{
+	struct slab_debug_private *priv = seq->private;
+	struct kmem_cache *s = priv->inode->i_private;
+	struct page *page = v;
+	void *addr = page_address(page);
+	void *p;
+	unsigned long *map = priv->map;
+	struct track *track;
+	depot_stack_handle_t handle;
+	unsigned long *entries;
+	unsigned int nr_entries, j;
+
+	for_each_object(p, s, addr, page->objects) {
+		seq_printf(seq, "Object: %pK ", p);
+		if (!test_bit(__obj_to_index(s, addr, p), map))
+			seq_puts(seq, "allocated\n");
+		else
+			seq_puts(seq, "free\n");
+
+		track = get_track(s, p, TRACK_ALLOC);
+		seq_printf(seq, "Last allocated: %pS age=%ld pid=%d cpu=%u\n",
+			(void *)track->addr, jiffies - track->when, track->pid, track->cpu);
+
+#ifdef CONFIG_STACKDEPOT
+			handle = READ_ONCE(track->handle);
+			if (handle) {
+				nr_entries = stack_depot_fetch(handle, &entries);
+				for (j = 0; j < nr_entries; j++)
+					seq_printf(seq, "\t%pS\n", (void *)entries[j]);
+			}
+#endif
+
+			track = get_track(s, p, TRACK_FREE);
+			seq_printf(seq, "Last free: %pS age=%ld pid=%d cpu=%u\n",
+				(void *)track->addr, jiffies - track->when, track->pid, track->cpu);
+
+#ifdef CONFIG_STACKDEPOT
+			handle = READ_ONCE(track->handle);
+			if (handle) {
+				nr_entries = stack_depot_fetch(handle, &entries);
+				for (j = 0; j < nr_entries; j++)
+					seq_printf(seq, "\t%pS\n", (void *)entries[j]);
+			}
+#endif
+			seq_puts(seq, "\n");
+	}
+	return 0;
+}
+
+static void *debugfs_all_objects_start(struct seq_file *m, loff_t *ppos)
+{
+	struct slab_debug_private *priv = m->private;
+	struct kmem_cache *s = priv->inode->i_private;
+	struct page *page;
+
+	priv->map = kmalloc(BITS_TO_LONGS(MAX_OBJS_PER_PAGE), GFP_KERNEL);
+
+	if (!priv->map)
+		return NULL;
+
+	if (!(s->flags & SLAB_STORE_USER))
+		return ERR_PTR(-EOPNOTSUPP);
+
+	page = next_page(priv);
+	return page;
+}
+
+static void *debugfs_all_objects_next(struct seq_file *m, void *v, loff_t *ppos)
+{
+	struct slab_debug_private *priv = m->private;
+	struct page *page;
+
+	if (v)
+		put_page(v);
+
+	++*ppos;
+	page = next_page(priv);
+
+	return page;
+}
+
+static void debugfs_all_objects_stop(struct seq_file *m, void *v)
+{
+	struct slab_debug_private *priv = m->private;
+	struct kmem_cache *s = priv->inode->i_private;
+
+	kfree(priv->map);
+
+	if (v && (s->flags & SLAB_STORE_USER))
+		put_page(v);
+}
+
+static const struct seq_operations debugfs_all_objects_ops = {
+	.start	= debugfs_all_objects_start,
+	.next	= debugfs_all_objects_next,
+	.stop	= debugfs_all_objects_stop,
+	.show	= debugfs_all_objects_show
+};
+
+static int debugfs_all_objects_open(struct inode *inode, struct file *file)
+{
+	struct slab_debug_private *priv = __seq_open_private(file,
+			&debugfs_all_objects_ops, sizeof(struct slab_debug_private));
+
+	if (!priv)
+		return -ENOMEM;
+
+	priv->inode = inode;
+	priv->nid = 0;
+	priv->field = FULL_LIST;
+
+	return 0;
+}
+
+static const struct file_operations debugfs_all_objects_fops = {
+	.open    = debugfs_all_objects_open,
+	.read    = seq_read,
+	.llseek  = seq_lseek,
+	.release = seq_release_private,
+};
+
 static int slab_debugfs_show(struct seq_file *seq, void *v)
 {
 	struct location *l;
@@ -6018,6 +6239,10 @@  static void debugfs_slab_add(struct kmem_cache *s)
 	debugfs_create_file("free_traces", 0400,
 		slab_cache_dir, s, &slab_debugfs_fops);
 
+	debugfs_create_file("all_objects", 0400,
+		slab_cache_dir, s, &debugfs_all_objects_fops);
+
+
 	if (!unmergeable)
 		/* Setup first alias */
 		debugfs_slab_alias(s, s->name);