bcachefs: bch_sb_field_recovery_passes

New superblock section for statistics on recovery passes - last time
ran (successfully), last runtime.

This will be used by self healing code to determine when to kick off
potentially expensive recovery passes.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2025-05-09 18:24:20 -04:00
parent 20a4b7f3b8
commit e21f997721
4 changed files with 146 additions and 56 deletions

View File

@ -497,7 +497,8 @@ struct bch_sb_field {
x(members_v2, 11) \
x(errors, 12) \
x(ext, 13) \
x(downgrade, 14)
x(downgrade, 14) \
x(recovery_passes, 15)
#include "alloc_background_format.h"
#include "dirent_format.h"

View File

@ -28,6 +28,121 @@ const char * const bch2_recovery_passes[] = {
NULL
};
static const u8 passes_to_stable_map[] = {
#define x(n, id, ...) [BCH_RECOVERY_PASS_##n] = BCH_RECOVERY_PASS_STABLE_##n,
BCH_RECOVERY_PASSES()
#undef x
};
static const u8 passes_from_stable_map[] = {
#define x(n, id, ...) [BCH_RECOVERY_PASS_STABLE_##n] = BCH_RECOVERY_PASS_##n,
BCH_RECOVERY_PASSES()
#undef x
};
static enum bch_recovery_pass_stable bch2_recovery_pass_to_stable(enum bch_recovery_pass pass)
{
return passes_to_stable_map[pass];
}
u64 bch2_recovery_passes_to_stable(u64 v)
{
u64 ret = 0;
for (unsigned i = 0; i < ARRAY_SIZE(passes_to_stable_map); i++)
if (v & BIT_ULL(i))
ret |= BIT_ULL(passes_to_stable_map[i]);
return ret;
}
static enum bch_recovery_pass bch2_recovery_pass_from_stable(enum bch_recovery_pass_stable pass)
{
return pass < ARRAY_SIZE(passes_from_stable_map)
? passes_from_stable_map[pass]
: 0;
}
u64 bch2_recovery_passes_from_stable(u64 v)
{
u64 ret = 0;
for (unsigned i = 0; i < ARRAY_SIZE(passes_from_stable_map); i++)
if (v & BIT_ULL(i))
ret |= BIT_ULL(passes_from_stable_map[i]);
return ret;
}
static int bch2_sb_recovery_passes_validate(struct bch_sb *sb, struct bch_sb_field *f,
enum bch_validate_flags flags, struct printbuf *err)
{
return 0;
}
static void bch2_sb_recovery_passes_to_text(struct printbuf *out,
struct bch_sb *sb,
struct bch_sb_field *f)
{
struct bch_sb_field_recovery_passes *r =
field_to_type(f, recovery_passes);
unsigned nr = recovery_passes_nr_entries(r);
if (out->nr_tabstops < 1)
printbuf_tabstop_push(out, 32);
if (out->nr_tabstops < 2)
printbuf_tabstop_push(out, 16);
prt_printf(out, "Pass\tLast run\tLast runtime\n");
for (struct recovery_pass_entry *i = r->start; i < r->start + nr; i++) {
if (!i->last_run)
continue;
unsigned idx = i - r->start;
prt_printf(out, "%s\t", bch2_recovery_passes[bch2_recovery_pass_from_stable(idx)]);
bch2_prt_datetime(out, le64_to_cpu(i->last_run));
prt_tab(out);
bch2_pr_time_units(out, le32_to_cpu(i->last_runtime) * NSEC_PER_SEC);
prt_newline(out);
}
}
static void bch2_sb_recovery_pass_complete(struct bch_fs *c,
enum bch_recovery_pass pass,
s64 start_time)
{
enum bch_recovery_pass_stable stable = bch2_recovery_pass_to_stable(pass);
s64 end_time = ktime_get_real_seconds();
mutex_lock(&c->sb_lock);
struct bch_sb_field_ext *ext = bch2_sb_field_get(c->disk_sb.sb, ext);
__clear_bit_le64(stable, ext->recovery_passes_required);
struct bch_sb_field_recovery_passes *r =
bch2_sb_field_get(c->disk_sb.sb, recovery_passes);
if (stable >= recovery_passes_nr_entries(r)) {
unsigned u64s = struct_size(r, start, stable + 1) / sizeof(u64);
r = bch2_sb_field_resize(&c->disk_sb, recovery_passes, u64s);
if (!r) {
bch_err(c, "error creating recovery_passes sb section");
goto out;
}
}
r->start[stable].last_run = cpu_to_le64(end_time);
r->start[stable].last_runtime = cpu_to_le32(max(0, end_time - start_time));
out:
bch2_write_super(c);
mutex_unlock(&c->sb_lock);
}
const struct bch_sb_field_ops bch_sb_field_ops_recovery_passes = {
.validate = bch2_sb_recovery_passes_validate,
.to_text = bch2_sb_recovery_passes_to_text
};
/* Fake recovery pass, so that scan_for_btree_nodes isn't 0: */
static int bch2_recovery_pass_empty(struct bch_fs *c)
{
@ -88,41 +203,6 @@ static struct recovery_pass_fn recovery_pass_fns[] = {
#undef x
};
static const u8 passes_to_stable_map[] = {
#define x(n, id, ...) [BCH_RECOVERY_PASS_##n] = BCH_RECOVERY_PASS_STABLE_##n,
BCH_RECOVERY_PASSES()
#undef x
};
static enum bch_recovery_pass_stable bch2_recovery_pass_to_stable(enum bch_recovery_pass pass)
{
return passes_to_stable_map[pass];
}
u64 bch2_recovery_passes_to_stable(u64 v)
{
u64 ret = 0;
for (unsigned i = 0; i < ARRAY_SIZE(passes_to_stable_map); i++)
if (v & BIT_ULL(i))
ret |= BIT_ULL(passes_to_stable_map[i]);
return ret;
}
u64 bch2_recovery_passes_from_stable(u64 v)
{
static const u8 map[] = {
#define x(n, id, ...) [BCH_RECOVERY_PASS_STABLE_##n] = BCH_RECOVERY_PASS_##n,
BCH_RECOVERY_PASSES()
#undef x
};
u64 ret = 0;
for (unsigned i = 0; i < ARRAY_SIZE(map); i++)
if (v & BIT_ULL(i))
ret |= BIT_ULL(map[i]);
return ret;
}
/*
* For when we need to rewind recovery passes and run a pass we skipped:
*/
@ -219,21 +299,6 @@ int bch2_run_explicit_recovery_pass_persistent(struct bch_fs *c,
return ret;
}
static void bch2_clear_recovery_pass_required(struct bch_fs *c,
enum bch_recovery_pass pass)
{
enum bch_recovery_pass_stable s = bch2_recovery_pass_to_stable(pass);
mutex_lock(&c->sb_lock);
struct bch_sb_field_ext *ext = bch2_sb_field_get(c->disk_sb.sb, ext);
if (test_bit_le64(s, ext->recovery_passes_required)) {
__clear_bit_le64(s, ext->recovery_passes_required);
bch2_write_super(c);
}
mutex_unlock(&c->sb_lock);
}
u64 bch2_fsck_recovery_passes(void)
{
u64 ret = 0;
@ -266,14 +331,19 @@ static bool should_run_recovery_pass(struct bch_fs *c, enum bch_recovery_pass pa
static int bch2_run_recovery_pass(struct bch_fs *c, enum bch_recovery_pass pass)
{
struct recovery_pass_fn *p = recovery_pass_fns + pass;
int ret;
if (!(p->when & PASS_SILENT))
bch2_print(c, KERN_INFO bch2_log_msg(c, "%s..."),
bch2_recovery_passes[pass]);
ret = p->fn(c);
s64 start_time = ktime_get_real_seconds();
int ret = p->fn(c);
if (ret)
return ret;
if (!test_bit(BCH_FS_error, &c->flags))
bch2_sb_recovery_pass_complete(c, pass, start_time);
if (!(p->when & PASS_SILENT))
bch2_print(c, KERN_CONT " done\n");
@ -326,9 +396,6 @@ int bch2_run_recovery_passes(struct bch_fs *c)
spin_unlock_irq(&c->recovery_pass_lock);
ret = bch2_run_recovery_pass(c, pass) ?:
bch2_journal_flush(&c->journal);
if (!ret && !test_bit(BCH_FS_error, &c->flags))
bch2_clear_recovery_pass_required(c, pass);
spin_lock_irq(&c->recovery_pass_lock);
if (c->next_recovery_pass < c->curr_recovery_pass) {

View File

@ -3,6 +3,8 @@
extern const char * const bch2_recovery_passes[];
extern const struct bch_sb_field_ops bch_sb_field_ops_recovery_passes;
u64 bch2_recovery_passes_to_stable(u64 v);
u64 bch2_recovery_passes_from_stable(u64 v);

View File

@ -81,4 +81,24 @@ enum bch_recovery_pass_stable {
#undef x
};
struct recovery_pass_entry {
__le64 last_run;
__le32 last_runtime;
__le32 flags;
};
struct bch_sb_field_recovery_passes {
struct bch_sb_field field;
struct recovery_pass_entry start[];
};
static inline unsigned
recovery_passes_nr_entries(struct bch_sb_field_recovery_passes *r)
{
return r
? ((vstruct_end(&r->field) - (void *) &r->start[0]) /
sizeof(struct recovery_pass_entry))
: 0;
}
#endif /* _BCACHEFS_RECOVERY_PASSES_FORMAT_H */