bcachefs: Async object debugging
authorKent Overstreet <kent.overstreet@linux.dev>
Mon, 21 Apr 2025 16:01:50 +0000 (12:01 -0400)
committerKent Overstreet <kent.overstreet@linux.dev>
Thu, 22 May 2025 00:14:29 +0000 (20:14 -0400)
Debugging infrastructure for async objs: this lets us easily create
fast_lists for various object types so they'll be visible in debugfs.

Add new object types to the BCH_ASYNC_OBJS_TYPES() enum, and drop a
pretty-printer wrapper in async_objs.c.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
fs/bcachefs/Kconfig
fs/bcachefs/Makefile
fs/bcachefs/async_objs.c [new file with mode: 0644]
fs/bcachefs/async_objs.h [new file with mode: 0644]
fs/bcachefs/async_objs_types.h [new file with mode: 0644]
fs/bcachefs/bcachefs.h
fs/bcachefs/debug.c
fs/bcachefs/debug.h
fs/bcachefs/errcode.h
fs/bcachefs/super.c

index a14e4a60b18738f6d0d334fd638089e3629f4357..8cb2b9d5da96f3acff2bfea076d0a74212302d57 100644 (file)
@@ -107,6 +107,10 @@ config BCACHEFS_TRANS_KMALLOC_TRACE
        bool "Trace bch2_trans_kmalloc() calls"
        depends on BCACHEFS_FS
 
+config BCACHEFS_ASYNC_OBJECT_LISTS
+       bool "Keep async objects on fast_lists for debugfs visibility"
+       depends on BCACHEFS_FS && DEBUG_FS
+
 config MEAN_AND_VARIANCE_UNIT_TEST
        tristate "mean_and_variance unit tests" if !KUNIT_ALL_TESTS
        depends on KUNIT
index 3be39845e4f658a7445b9a087738645a968def77..93c8ee5425c8ddccba2b5f288f83c2413e3e5b34 100644 (file)
@@ -99,6 +99,8 @@ bcachefs-y            :=      \
        varint.o                \
        xattr.o
 
+bcachefs-$(CONFIG_BCACHEFS_ASYNC_OBJECT_LISTS)   += async_objs.o
+
 obj-$(CONFIG_MEAN_AND_VARIANCE_UNIT_TEST)   += mean_and_variance_test.o
 
 # Silence "note: xyz changed in GCC X.X" messages
diff --git a/fs/bcachefs/async_objs.c b/fs/bcachefs/async_objs.c
new file mode 100644 (file)
index 0000000..8d78f39
--- /dev/null
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Async obj debugging: keep asynchronous objects on (very fast) lists, make
+ * them visibile in debugfs:
+ */
+
+#include "bcachefs.h"
+#include "async_objs.h"
+#include "btree_io.h"
+#include "debug.h"
+#include "io_read.h"
+
+#include <linux/debugfs.h>
+
+static int bch2_async_obj_list_open(struct inode *inode, struct file *file)
+{
+       struct async_obj_list *list = inode->i_private;
+       struct dump_iter *i;
+
+       i = kzalloc(sizeof(struct dump_iter), GFP_KERNEL);
+       if (!i)
+               return -ENOMEM;
+
+       file->private_data = i;
+       i->from = POS_MIN;
+       i->iter = 0;
+       i->c    = container_of(list, struct bch_fs, async_objs[list->idx]);
+       i->list = list;
+       i->buf  = PRINTBUF;
+       return 0;
+}
+
+static ssize_t bch2_async_obj_list_read(struct file *file, char __user *buf,
+                                       size_t size, loff_t *ppos)
+{
+       struct dump_iter *i = file->private_data;
+       struct async_obj_list *list = i->list;
+       ssize_t ret = 0;
+
+       i->ubuf = buf;
+       i->size = size;
+       i->ret  = 0;
+
+       struct genradix_iter iter;
+       void *obj;
+       fast_list_for_each_from(&list->list, iter, obj, i->iter) {
+               ret = bch2_debugfs_flush_buf(i);
+               if (ret)
+                       return ret;
+
+               if (!i->size)
+                       break;
+
+               list->obj_to_text(&i->buf, obj);
+       }
+
+       if (i->buf.allocation_failure)
+               ret = -ENOMEM;
+       else
+               i->iter = iter.pos;
+
+       if (!ret)
+               ret = bch2_debugfs_flush_buf(i);
+
+       return ret ?: i->ret;
+}
+
+__maybe_unused
+static const struct file_operations async_obj_ops = {
+       .owner          = THIS_MODULE,
+       .open           = bch2_async_obj_list_open,
+       .release        = bch2_dump_release,
+       .read           = bch2_async_obj_list_read,
+};
+
+void bch2_fs_async_obj_debugfs_init(struct bch_fs *c)
+{
+       c->async_obj_dir = debugfs_create_dir("async_objs", c->fs_debug_dir);
+
+#define x(n) debugfs_create_file(#n, 0400, c->async_obj_dir,           \
+                           &c->async_objs[BCH_ASYNC_OBJ_LIST_##n], &async_obj_ops);
+       BCH_ASYNC_OBJ_LISTS()
+#undef x
+}
+
+void bch2_fs_async_obj_exit(struct bch_fs *c)
+{
+       for (unsigned i = 0; i < ARRAY_SIZE(c->async_objs); i++)
+               fast_list_exit(&c->async_objs[i].list);
+}
+
+int bch2_fs_async_obj_init(struct bch_fs *c)
+{
+       for (unsigned i = 0; i < ARRAY_SIZE(c->async_objs); i++) {
+               if (fast_list_init(&c->async_objs[i].list))
+                       return -BCH_ERR_ENOMEM_async_obj_init;
+               c->async_objs[i].idx = i;
+       }
+
+#define x(n) c->async_objs[BCH_ASYNC_OBJ_LIST_##n].obj_to_text = n##_obj_to_text;
+       BCH_ASYNC_OBJ_LISTS()
+#undef x
+
+       return 0;
+}
diff --git a/fs/bcachefs/async_objs.h b/fs/bcachefs/async_objs.h
new file mode 100644 (file)
index 0000000..cd6489b
--- /dev/null
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _BCACHEFS_ASYNC_OBJS_H
+#define _BCACHEFS_ASYNC_OBJS_H
+
+#ifdef CONFIG_BCACHEFS_ASYNC_OBJECT_LISTS
+static inline void __async_object_list_del(struct fast_list *head, unsigned idx)
+{
+       fast_list_remove(head, idx);
+}
+
+static inline int __async_object_list_add(struct fast_list *head, void *obj, unsigned *idx)
+{
+       int ret = fast_list_add(head, obj);
+       *idx = ret > 0 ? ret : 0;
+       return ret < 0 ? ret : 0;
+}
+
+#define async_object_list_del(_c, _list, idx)          \
+       __async_object_list_del(&(_c)->async_objs[BCH_ASYNC_OBJ_LIST_##_list].list, idx)
+
+#define async_object_list_add(_c, _list, obj, idx)             \
+       __async_object_list_add(&(_c)->async_objs[BCH_ASYNC_OBJ_LIST_##_list].list, obj, idx)
+
+void bch2_fs_async_obj_debugfs_init(struct bch_fs *);
+void bch2_fs_async_obj_exit(struct bch_fs *);
+int bch2_fs_async_obj_init(struct bch_fs *);
+
+#else /* CONFIG_BCACHEFS_ASYNC_OBJECT_LISTS */
+
+#define async_object_list_del(_c, _n, idx)             do {} while (0)
+
+static inline int __async_object_list_add(void)
+{
+       return 0;
+}
+#define async_object_list_add(_c, _n, obj, idx)                __async_object_list_add()
+
+static inline void bch2_fs_async_obj_debugfs_init(struct bch_fs *c) {}
+static inline void bch2_fs_async_obj_exit(struct bch_fs *c) {}
+static inline int bch2_fs_async_obj_init(struct bch_fs *c) { return 0; }
+
+#endif /* CONFIG_BCACHEFS_ASYNC_OBJECT_LISTS */
+
+#endif /* _BCACHEFS_ASYNC_OBJS_H */
diff --git a/fs/bcachefs/async_objs_types.h b/fs/bcachefs/async_objs_types.h
new file mode 100644 (file)
index 0000000..28cb73e
--- /dev/null
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _BCACHEFS_ASYNC_OBJS_TYPES_H
+#define _BCACHEFS_ASYNC_OBJS_TYPES_H
+
+#define BCH_ASYNC_OBJ_LISTS()
+
+enum bch_async_obj_lists {
+#define x(n)           BCH_ASYNC_OBJ_LIST_##n,
+       BCH_ASYNC_OBJ_LISTS()
+#undef x
+       BCH_ASYNC_OBJ_NR
+};
+
+struct async_obj_list {
+       struct fast_list        list;
+       void                    (*obj_to_text)(struct printbuf *, void *);
+       unsigned                idx;
+};
+
+#endif /* _BCACHEFS_ASYNC_OBJS_TYPES_H */
index 3d18dbe0d6f5c99a7e621e912a116e5c92b306b7..94e3edd932e3c11e19a17dd3faea6a2ab7066ebd 100644 (file)
 #include "btree_journal_iter_types.h"
 #include "disk_accounting_types.h"
 #include "errcode.h"
+#include "fast_list.h"
 #include "fifo.h"
 #include "nocow_locking_types.h"
 #include "opts.h"
@@ -474,6 +475,7 @@ enum bch_time_stats {
 };
 
 #include "alloc_types.h"
+#include "async_objs_types.h"
 #include "btree_gc_types.h"
 #include "btree_types.h"
 #include "btree_node_scan_types.h"
@@ -1027,6 +1029,10 @@ struct bch_fs {
                                nocow_locks;
        struct rhashtable       promote_table;
 
+#ifdef CONFIG_BCACHEFS_ASYNC_OBJECT_LISTS
+       struct async_obj_list   async_objs[BCH_ASYNC_OBJ_NR];
+#endif
+
        mempool_t               compression_bounce[2];
        mempool_t               compress_workspace[BCH_COMPRESSION_OPT_NR];
        size_t                  zstd_workspace_size;
@@ -1115,6 +1121,7 @@ struct bch_fs {
        /* DEBUG JUNK */
        struct dentry           *fs_debug_dir;
        struct dentry           *btree_debug_dir;
+       struct dentry           *async_obj_dir;
        struct btree_debug      btree_debug[BTREE_ID_NR];
        struct btree            *verify_data;
        struct btree_node       *verify_ondisk;
index 4cbb19c36fa16f9382cb76fd2428963322f41030..079bc2b359cd62593755055debaf5b747837e51e 100644 (file)
@@ -8,6 +8,7 @@
 
 #include "bcachefs.h"
 #include "alloc_foreground.h"
+#include "async_objs.h"
 #include "bkey_methods.h"
 #include "btree_cache.h"
 #include "btree_io.h"
@@ -16,6 +17,7 @@
 #include "btree_update.h"
 #include "btree_update_interior.h"
 #include "buckets.h"
+#include "data_update.h"
 #include "debug.h"
 #include "error.h"
 #include "extents.h"
@@ -306,23 +308,7 @@ out:
 
 #ifdef CONFIG_DEBUG_FS
 
-/* XXX: bch_fs refcounting */
-
-struct dump_iter {
-       struct bch_fs           *c;
-       enum btree_id           id;
-       struct bpos             from;
-       struct bpos             prev_node;
-       u64                     iter;
-
-       struct printbuf         buf;
-
-       char __user             *ubuf;  /* destination user buffer */
-       size_t                  size;   /* size of requested read */
-       ssize_t                 ret;    /* bytes read so far */
-};
-
-static ssize_t flush_buf(struct dump_iter *i)
+ssize_t bch2_debugfs_flush_buf(struct dump_iter *i)
 {
        if (i->buf.pos) {
                size_t bytes = min_t(size_t, i->buf.pos, i->size);
@@ -360,7 +346,7 @@ static int bch2_dump_open(struct inode *inode, struct file *file)
        return 0;
 }
 
-static int bch2_dump_release(struct inode *inode, struct file *file)
+int bch2_dump_release(struct inode *inode, struct file *file)
 {
        struct dump_iter *i = file->private_data;
 
@@ -378,7 +364,7 @@ static ssize_t bch2_read_btree(struct file *file, char __user *buf,
        i->size = size;
        i->ret  = 0;
 
-       return flush_buf(i) ?:
+       return bch2_debugfs_flush_buf(i) ?:
                bch2_trans_run(i->c,
                        for_each_btree_key(trans, iter, i->id, i->from,
                                           BTREE_ITER_prefetch|
@@ -387,7 +373,7 @@ static ssize_t bch2_read_btree(struct file *file, char __user *buf,
                                prt_newline(&i->buf);
                                bch2_trans_unlock(trans);
                                i->from = bpos_successor(iter.pos);
-                               flush_buf(i);
+                               bch2_debugfs_flush_buf(i);
                        }))) ?:
                i->ret;
 }
@@ -408,7 +394,7 @@ static ssize_t bch2_read_btree_formats(struct file *file, char __user *buf,
        i->size = size;
        i->ret  = 0;
 
-       ssize_t ret = flush_buf(i);
+       ssize_t ret = bch2_debugfs_flush_buf(i);
        if (ret)
                return ret;
 
@@ -422,7 +408,7 @@ static ssize_t bch2_read_btree_formats(struct file *file, char __user *buf,
                                ? bpos_successor(b->key.k.p)
                                : b->key.k.p;
 
-                       drop_locks_do(trans, flush_buf(i));
+                       drop_locks_do(trans, bch2_debugfs_flush_buf(i));
                }))) ?: i->ret;
 }
 
@@ -442,7 +428,7 @@ static ssize_t bch2_read_bfloat_failed(struct file *file, char __user *buf,
        i->size = size;
        i->ret  = 0;
 
-       return flush_buf(i) ?:
+       return bch2_debugfs_flush_buf(i) ?:
                bch2_trans_run(i->c,
                        for_each_btree_key(trans, iter, i->id, i->from,
                                           BTREE_ITER_prefetch|
@@ -460,7 +446,7 @@ static ssize_t bch2_read_bfloat_failed(struct file *file, char __user *buf,
                                bch2_bfloat_to_text(&i->buf, l->b, _k);
                                bch2_trans_unlock(trans);
                                i->from = bpos_successor(iter.pos);
-                               flush_buf(i);
+                               bch2_debugfs_flush_buf(i);
                        }))) ?:
                i->ret;
 }
@@ -521,7 +507,7 @@ static ssize_t bch2_cached_btree_nodes_read(struct file *file, char __user *buf,
                struct rhash_head *pos;
                struct btree *b;
 
-               ret = flush_buf(i);
+               ret = bch2_debugfs_flush_buf(i);
                if (ret)
                        return ret;
 
@@ -544,7 +530,7 @@ static ssize_t bch2_cached_btree_nodes_read(struct file *file, char __user *buf,
                ret = -ENOMEM;
 
        if (!ret)
-               ret = flush_buf(i);
+               ret = bch2_debugfs_flush_buf(i);
 
        return ret ?: i->ret;
 }
@@ -618,7 +604,7 @@ restart:
 
                closure_put(&trans->ref);
 
-               ret = flush_buf(i);
+               ret = bch2_debugfs_flush_buf(i);
                if (ret)
                        goto unlocked;
 
@@ -631,7 +617,7 @@ unlocked:
                ret = -ENOMEM;
 
        if (!ret)
-               ret = flush_buf(i);
+               ret = bch2_debugfs_flush_buf(i);
 
        return ret ?: i->ret;
 }
@@ -656,7 +642,7 @@ static ssize_t bch2_journal_pins_read(struct file *file, char __user *buf,
        i->ret  = 0;
 
        while (1) {
-               err = flush_buf(i);
+               err = bch2_debugfs_flush_buf(i);
                if (err)
                        return err;
 
@@ -699,7 +685,7 @@ static ssize_t bch2_btree_updates_read(struct file *file, char __user *buf,
                i->iter++;
        }
 
-       err = flush_buf(i);
+       err = bch2_debugfs_flush_buf(i);
        if (err)
                return err;
 
@@ -757,7 +743,7 @@ static ssize_t btree_transaction_stats_read(struct file *file, char __user *buf,
        while (1) {
                struct btree_transaction_stats *s = &c->btree_transaction_stats[i->iter];
 
-               err = flush_buf(i);
+               err = bch2_debugfs_flush_buf(i);
                if (err)
                        return err;
 
@@ -878,7 +864,7 @@ static ssize_t bch2_simple_print(struct file *file, char __user *buf,
                ret = -ENOMEM;
 
        if (!ret)
-               ret = flush_buf(i);
+               ret = bch2_debugfs_flush_buf(i);
 
        return ret ?: i->ret;
 }
@@ -967,6 +953,8 @@ void bch2_fs_debug_init(struct bch_fs *c)
        debugfs_create_file("write_points", 0400, c->fs_debug_dir,
                            c->btree_debug, &write_points_ops);
 
+       bch2_fs_async_obj_debugfs_init(c);
+
        c->btree_debug_dir = debugfs_create_dir("btrees", c->fs_debug_dir);
        if (IS_ERR_OR_NULL(c->btree_debug_dir))
                return;
index 2c37143b5fd1803e20eeddead3ad5160711d7a27..52dbea7367092b82d77ef4b8d18f0f605b5089c2 100644 (file)
@@ -19,6 +19,24 @@ static inline void bch2_btree_verify(struct bch_fs *c, struct btree *b)
 }
 
 #ifdef CONFIG_DEBUG_FS
+struct dump_iter {
+       struct bch_fs           *c;
+       struct async_obj_list   *list;
+       enum btree_id           id;
+       struct bpos             from;
+       struct bpos             prev_node;
+       u64                     iter;
+
+       struct printbuf         buf;
+
+       char __user             *ubuf;  /* destination user buffer */
+       size_t                  size;   /* size of requested read */
+       ssize_t                 ret;    /* bytes read so far */
+};
+
+ssize_t bch2_debugfs_flush_buf(struct dump_iter *);
+int bch2_dump_release(struct inode *, struct file *);
+
 void bch2_fs_debug_exit(struct bch_fs *);
 void bch2_fs_debug_init(struct bch_fs *);
 #else
index 6a4b3fe9ea9954f1697de088b6b8be8b30b43eda..1a52edc7c8d849fc84e2902e802db436a4f7253a 100644 (file)
@@ -53,6 +53,7 @@
        x(ENOMEM,                       ENOMEM_dio_write_bioset_init)           \
        x(ENOMEM,                       ENOMEM_nocow_flush_bioset_init)         \
        x(ENOMEM,                       ENOMEM_promote_table_init)              \
+       x(ENOMEM,                       ENOMEM_async_obj_init)                  \
        x(ENOMEM,                       ENOMEM_compression_bounce_read_init)    \
        x(ENOMEM,                       ENOMEM_compression_bounce_write_init)   \
        x(ENOMEM,                       ENOMEM_compression_workspace_init)      \
index bed0f8a802128245e34b4da3fce185189b263649..f29965469b28f8cd082153c6934ab321cdc5224c 100644 (file)
@@ -10,6 +10,7 @@
 #include "bcachefs.h"
 #include "alloc_background.h"
 #include "alloc_foreground.h"
+#include "async_objs.h"
 #include "bkey_sort.h"
 #include "btree_cache.h"
 #include "btree_gc.h"
@@ -579,6 +580,7 @@ static void __bch2_fs_free(struct bch_fs *c)
        bch2_free_pending_node_rewrites(c);
        bch2_free_fsck_errs(c);
        bch2_fs_accounting_exit(c);
+       bch2_fs_async_obj_exit(c);
        bch2_fs_sb_errors_exit(c);
        bch2_fs_counters_exit(c);
        bch2_fs_snapshots_exit(c);
@@ -971,6 +973,7 @@ static struct bch_fs *bch2_fs_alloc(struct bch_sb *sb, struct bch_opts opts,
        }
 
        ret =
+           bch2_fs_async_obj_init(c) ?:
            bch2_fs_btree_cache_init(c) ?:
            bch2_fs_btree_iter_init(c) ?:
            bch2_fs_btree_key_cache_init(&c->btree_key_cache) ?: