c9d01179e1
Here's the second big bcachefs pull request. This brings your tree up to date with my master branch, which is what existing bcachefs users are currently running. All but the last few patches have been in linux-next, those being small fixes. Test results from my dashboard: https://evilpiepirate.org/~testdashboard/ci?commit=c7046ed0cf9bb33599aa7e72e7b67bba4be42d64 New features: - rebalance_work btree (and metadata version 1.3): the rebalance thread no longer has to scan to find extents that need processing - big scalability improvement. - sb_errors superblock section: this adds counters for each fsck error type, since filesystem creation, along with the date of the most recent error. It'll get us better bug reports (since users do not typically report errors that fsck was able to fix), and I might add telemetry for this in the future. Fixes include: - multiple snapshot deletion fixes - members_v2 fixups - deleted_inodes btree fixes - copygc thread no longer spins when a device is full but has no fragmented buckets (i.e. rebalance needs to move data around instead) - a fix for a memory reclaim issue with the btree key cache: we're now careful not to hold the srcu read lock that blocks key cache reclaim for too long - an early allocator locking fix, from Brian - endianness fixes, from Brian - CONFIG_BCACHEFS_DEBUG_TRANSACTIONS no longer defaults to y, a big performance improvement on multithreaded workloads -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEKnAFLkS8Qha+jvQrE6szbY3KbnYFAmVH9xYACgkQE6szbY3K bnahLRAAiNRZL73SQ+MW79o4yPqGwt0Eyy/mvoiGpZf1B8uXp0oZ55j2w3l887Uf LeM03mInAYCPdyp/d4vxqIr96j9BODmRRl8sEkkGdJDzokLG+22F0ovOe45KWTxL kBoNdng/O/oeOe/1K7taP3KzBvMx2nOF6oA+xfgyCjECMArAIXek0iocyEUR4Ywd vGKhLNn1k2c+94wacnDYwjjdcLBxoqxsFXlpu6V0BcaY+DX4J3aBaGmj75KEoCI0 VbBOzxrOO4QzJrzW2+hxZZWgGyvReCkBJvqfORfuPxiSbFobTim10MdfZOAMQA1U Xr1FTEpK1wMX0/pPVgZRqaOsttC+yc/SsfPNgSxybgHPbDlMLaakDHjvYssbKOYG urDWSMG5yCsktSLj95SXsvUFKZaZFD72SKBNdgdt/nZjwTHuNQ7IkdrMwIrCQ/PT Ifn50UrR/Ahd8RAd5tyNCPw6U9VfwnxACSNl2KA7ONKpvHb+gSt1JsJTDyz1+gN9 nFVrw1SHKQ6EIV6XhVon/5DEuRTzqoYGWoN08FHEUq9fBlvnVpmbJErCQMplOjz9 OQnAfpJH4YqkpXyjFAjP1V0An+RUn8QvDgXNqC9TyvCYuOliVFuil4y7/c+7oIQU NEoz+jVLenqsGOGAbduI4/Q567COojRgwEvbebSIxSImXuhCNj4= =Lo4N -----END PGP SIGNATURE----- Merge tag 'bcachefs-2023-11-5' of https://evilpiepirate.org/git/bcachefs Pull more bcachefs updates from Kent Overstreet: "Here's the second big bcachefs pull request. This brings your tree up to date with my master branch, which is what existing bcachefs users are currently running. New features: - rebalance_work btree (and metadata version 1.3): the rebalance thread no longer has to scan to find extents that need processing - big scalability improvement. - sb_errors superblock section: this adds counters for each fsck error type, since filesystem creation, along with the date of the most recent error. It'll get us better bug reports (since users do not typically report errors that fsck was able to fix), and I might add telemetry for this in the future. Fixes include: - multiple snapshot deletion fixes - members_v2 fixups - deleted_inodes btree fixes - copygc thread no longer spins when a device is full but has no fragmented buckets (i.e. rebalance needs to move data around instead) - a fix for a memory reclaim issue with the btree key cache: we're now careful not to hold the srcu read lock that blocks key cache reclaim for too long - an early allocator locking fix, from Brian - endianness fixes, from Brian - CONFIG_BCACHEFS_DEBUG_TRANSACTIONS no longer defaults to y, a big performance improvement on multithreaded workloads" * tag 'bcachefs-2023-11-5' of https://evilpiepirate.org/git/bcachefs: (70 commits) bcachefs: Improve stripe checksum error message bcachefs: Simplify, fix bch2_backpointer_get_key() bcachefs: kill thing_it_points_to arg to backpointer_not_found() bcachefs: bch2_ec_read_extent() now takes btree_trans bcachefs: bch2_stripe_to_text() now prints ptr gens bcachefs: Don't iterate over journal entries just for btree roots bcachefs: Break up bch2_journal_write() bcachefs: Replace ERANGE with private error codes bcachefs: bkey_copy() is no longer a macro bcachefs: x-macro-ify inode flags enum bcachefs: Convert bch2_fs_open() to darray bcachefs: Move __bch2_members_v2_get_mut to sb-members.h bcachefs: bch2_prt_datetime() bcachefs: CONFIG_BCACHEFS_DEBUG_TRANSACTIONS no longer defaults to y bcachefs: Add a comment for BTREE_INSERT_NOJOURNAL usage bcachefs: rebalance_work btree is not a snapshots btree bcachefs: Add missing printk newlines bcachefs: Fix recovery when forced to use JSET_NO_FLUSH journal entry bcachefs: .get_parent() should return an error pointer bcachefs: Fix bch2_delete_dead_inodes() ...
1035 lines
23 KiB
C
1035 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* bcache sysfs interfaces
|
|
*
|
|
* Copyright 2010, 2011 Kent Overstreet <kent.overstreet@gmail.com>
|
|
* Copyright 2012 Google, Inc.
|
|
*/
|
|
|
|
#ifndef NO_BCACHEFS_SYSFS
|
|
|
|
#include "bcachefs.h"
|
|
#include "alloc_background.h"
|
|
#include "alloc_foreground.h"
|
|
#include "sysfs.h"
|
|
#include "btree_cache.h"
|
|
#include "btree_io.h"
|
|
#include "btree_iter.h"
|
|
#include "btree_key_cache.h"
|
|
#include "btree_update.h"
|
|
#include "btree_update_interior.h"
|
|
#include "btree_gc.h"
|
|
#include "buckets.h"
|
|
#include "clock.h"
|
|
#include "disk_groups.h"
|
|
#include "ec.h"
|
|
#include "inode.h"
|
|
#include "journal.h"
|
|
#include "keylist.h"
|
|
#include "move.h"
|
|
#include "movinggc.h"
|
|
#include "nocow_locking.h"
|
|
#include "opts.h"
|
|
#include "rebalance.h"
|
|
#include "replicas.h"
|
|
#include "super-io.h"
|
|
#include "tests.h"
|
|
|
|
#include <linux/blkdev.h>
|
|
#include <linux/sort.h>
|
|
#include <linux/sched/clock.h>
|
|
|
|
#include "util.h"
|
|
|
|
#define SYSFS_OPS(type) \
|
|
const struct sysfs_ops type ## _sysfs_ops = { \
|
|
.show = type ## _show, \
|
|
.store = type ## _store \
|
|
}
|
|
|
|
#define SHOW(fn) \
|
|
static ssize_t fn ## _to_text(struct printbuf *, \
|
|
struct kobject *, struct attribute *); \
|
|
\
|
|
static ssize_t fn ## _show(struct kobject *kobj, struct attribute *attr,\
|
|
char *buf) \
|
|
{ \
|
|
struct printbuf out = PRINTBUF; \
|
|
ssize_t ret = fn ## _to_text(&out, kobj, attr); \
|
|
\
|
|
if (out.pos && out.buf[out.pos - 1] != '\n') \
|
|
prt_newline(&out); \
|
|
\
|
|
if (!ret && out.allocation_failure) \
|
|
ret = -ENOMEM; \
|
|
\
|
|
if (!ret) { \
|
|
ret = min_t(size_t, out.pos, PAGE_SIZE - 1); \
|
|
memcpy(buf, out.buf, ret); \
|
|
} \
|
|
printbuf_exit(&out); \
|
|
return bch2_err_class(ret); \
|
|
} \
|
|
\
|
|
static ssize_t fn ## _to_text(struct printbuf *out, struct kobject *kobj,\
|
|
struct attribute *attr)
|
|
|
|
#define STORE(fn) \
|
|
static ssize_t fn ## _store_inner(struct kobject *, struct attribute *,\
|
|
const char *, size_t); \
|
|
\
|
|
static ssize_t fn ## _store(struct kobject *kobj, struct attribute *attr,\
|
|
const char *buf, size_t size) \
|
|
{ \
|
|
return bch2_err_class(fn##_store_inner(kobj, attr, buf, size)); \
|
|
} \
|
|
\
|
|
static ssize_t fn ## _store_inner(struct kobject *kobj, struct attribute *attr,\
|
|
const char *buf, size_t size)
|
|
|
|
#define __sysfs_attribute(_name, _mode) \
|
|
static struct attribute sysfs_##_name = \
|
|
{ .name = #_name, .mode = _mode }
|
|
|
|
#define write_attribute(n) __sysfs_attribute(n, 0200)
|
|
#define read_attribute(n) __sysfs_attribute(n, 0444)
|
|
#define rw_attribute(n) __sysfs_attribute(n, 0644)
|
|
|
|
#define sysfs_printf(file, fmt, ...) \
|
|
do { \
|
|
if (attr == &sysfs_ ## file) \
|
|
prt_printf(out, fmt "\n", __VA_ARGS__); \
|
|
} while (0)
|
|
|
|
#define sysfs_print(file, var) \
|
|
do { \
|
|
if (attr == &sysfs_ ## file) \
|
|
snprint(out, var); \
|
|
} while (0)
|
|
|
|
#define sysfs_hprint(file, val) \
|
|
do { \
|
|
if (attr == &sysfs_ ## file) \
|
|
prt_human_readable_s64(out, val); \
|
|
} while (0)
|
|
|
|
#define sysfs_strtoul(file, var) \
|
|
do { \
|
|
if (attr == &sysfs_ ## file) \
|
|
return strtoul_safe(buf, var) ?: (ssize_t) size; \
|
|
} while (0)
|
|
|
|
#define sysfs_strtoul_clamp(file, var, min, max) \
|
|
do { \
|
|
if (attr == &sysfs_ ## file) \
|
|
return strtoul_safe_clamp(buf, var, min, max) \
|
|
?: (ssize_t) size; \
|
|
} while (0)
|
|
|
|
#define strtoul_or_return(cp) \
|
|
({ \
|
|
unsigned long _v; \
|
|
int _r = kstrtoul(cp, 10, &_v); \
|
|
if (_r) \
|
|
return _r; \
|
|
_v; \
|
|
})
|
|
|
|
write_attribute(trigger_gc);
|
|
write_attribute(trigger_discards);
|
|
write_attribute(trigger_invalidates);
|
|
write_attribute(prune_cache);
|
|
write_attribute(btree_wakeup);
|
|
rw_attribute(btree_gc_periodic);
|
|
rw_attribute(gc_gens_pos);
|
|
|
|
read_attribute(uuid);
|
|
read_attribute(minor);
|
|
read_attribute(bucket_size);
|
|
read_attribute(first_bucket);
|
|
read_attribute(nbuckets);
|
|
rw_attribute(durability);
|
|
read_attribute(io_done);
|
|
read_attribute(io_errors);
|
|
write_attribute(io_errors_reset);
|
|
|
|
read_attribute(io_latency_read);
|
|
read_attribute(io_latency_write);
|
|
read_attribute(io_latency_stats_read);
|
|
read_attribute(io_latency_stats_write);
|
|
read_attribute(congested);
|
|
|
|
read_attribute(btree_write_stats);
|
|
|
|
read_attribute(btree_cache_size);
|
|
read_attribute(compression_stats);
|
|
read_attribute(journal_debug);
|
|
read_attribute(btree_updates);
|
|
read_attribute(btree_cache);
|
|
read_attribute(btree_key_cache);
|
|
read_attribute(stripes_heap);
|
|
read_attribute(open_buckets);
|
|
read_attribute(open_buckets_partial);
|
|
read_attribute(write_points);
|
|
read_attribute(nocow_lock_table);
|
|
|
|
#ifdef BCH_WRITE_REF_DEBUG
|
|
read_attribute(write_refs);
|
|
|
|
static const char * const bch2_write_refs[] = {
|
|
#define x(n) #n,
|
|
BCH_WRITE_REFS()
|
|
#undef x
|
|
NULL
|
|
};
|
|
|
|
static void bch2_write_refs_to_text(struct printbuf *out, struct bch_fs *c)
|
|
{
|
|
bch2_printbuf_tabstop_push(out, 24);
|
|
|
|
for (unsigned i = 0; i < ARRAY_SIZE(c->writes); i++) {
|
|
prt_str(out, bch2_write_refs[i]);
|
|
prt_tab(out);
|
|
prt_printf(out, "%li", atomic_long_read(&c->writes[i]));
|
|
prt_newline(out);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
read_attribute(internal_uuid);
|
|
read_attribute(disk_groups);
|
|
|
|
read_attribute(has_data);
|
|
read_attribute(alloc_debug);
|
|
|
|
#define x(t, n, ...) read_attribute(t);
|
|
BCH_PERSISTENT_COUNTERS()
|
|
#undef x
|
|
|
|
rw_attribute(discard);
|
|
rw_attribute(label);
|
|
|
|
rw_attribute(copy_gc_enabled);
|
|
read_attribute(copy_gc_wait);
|
|
|
|
rw_attribute(rebalance_enabled);
|
|
sysfs_pd_controller_attribute(rebalance);
|
|
read_attribute(rebalance_status);
|
|
rw_attribute(promote_whole_extents);
|
|
|
|
read_attribute(new_stripes);
|
|
|
|
read_attribute(io_timers_read);
|
|
read_attribute(io_timers_write);
|
|
|
|
read_attribute(moving_ctxts);
|
|
|
|
#ifdef CONFIG_BCACHEFS_TESTS
|
|
write_attribute(perf_test);
|
|
#endif /* CONFIG_BCACHEFS_TESTS */
|
|
|
|
#define x(_name) \
|
|
static struct attribute sysfs_time_stat_##_name = \
|
|
{ .name = #_name, .mode = 0444 };
|
|
BCH_TIME_STATS()
|
|
#undef x
|
|
|
|
static struct attribute sysfs_state_rw = {
|
|
.name = "state",
|
|
.mode = 0444,
|
|
};
|
|
|
|
static size_t bch2_btree_cache_size(struct bch_fs *c)
|
|
{
|
|
size_t ret = 0;
|
|
struct btree *b;
|
|
|
|
mutex_lock(&c->btree_cache.lock);
|
|
list_for_each_entry(b, &c->btree_cache.live, list)
|
|
ret += btree_bytes(c);
|
|
|
|
mutex_unlock(&c->btree_cache.lock);
|
|
return ret;
|
|
}
|
|
|
|
static int bch2_compression_stats_to_text(struct printbuf *out, struct bch_fs *c)
|
|
{
|
|
struct btree_trans *trans;
|
|
struct btree_iter iter;
|
|
struct bkey_s_c k;
|
|
enum btree_id id;
|
|
u64 nr_uncompressed_extents = 0,
|
|
nr_compressed_extents = 0,
|
|
nr_incompressible_extents = 0,
|
|
uncompressed_sectors = 0,
|
|
incompressible_sectors = 0,
|
|
compressed_sectors_compressed = 0,
|
|
compressed_sectors_uncompressed = 0;
|
|
int ret = 0;
|
|
|
|
if (!test_bit(BCH_FS_STARTED, &c->flags))
|
|
return -EPERM;
|
|
|
|
trans = bch2_trans_get(c);
|
|
|
|
for (id = 0; id < BTREE_ID_NR; id++) {
|
|
if (!btree_type_has_ptrs(id))
|
|
continue;
|
|
|
|
for_each_btree_key(trans, iter, id, POS_MIN,
|
|
BTREE_ITER_ALL_SNAPSHOTS, k, ret) {
|
|
struct bkey_ptrs_c ptrs = bch2_bkey_ptrs_c(k);
|
|
const union bch_extent_entry *entry;
|
|
struct extent_ptr_decoded p;
|
|
bool compressed = false, uncompressed = false, incompressible = false;
|
|
|
|
bkey_for_each_ptr_decode(k.k, ptrs, p, entry) {
|
|
switch (p.crc.compression_type) {
|
|
case BCH_COMPRESSION_TYPE_none:
|
|
uncompressed = true;
|
|
uncompressed_sectors += k.k->size;
|
|
break;
|
|
case BCH_COMPRESSION_TYPE_incompressible:
|
|
incompressible = true;
|
|
incompressible_sectors += k.k->size;
|
|
break;
|
|
default:
|
|
compressed_sectors_compressed +=
|
|
p.crc.compressed_size;
|
|
compressed_sectors_uncompressed +=
|
|
p.crc.uncompressed_size;
|
|
compressed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (incompressible)
|
|
nr_incompressible_extents++;
|
|
else if (uncompressed)
|
|
nr_uncompressed_extents++;
|
|
else if (compressed)
|
|
nr_compressed_extents++;
|
|
}
|
|
bch2_trans_iter_exit(trans, &iter);
|
|
}
|
|
|
|
bch2_trans_put(trans);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
prt_printf(out, "uncompressed:\n");
|
|
prt_printf(out, " nr extents: %llu\n", nr_uncompressed_extents);
|
|
prt_printf(out, " size: ");
|
|
prt_human_readable_u64(out, uncompressed_sectors << 9);
|
|
prt_printf(out, "\n");
|
|
|
|
prt_printf(out, "compressed:\n");
|
|
prt_printf(out, " nr extents: %llu\n", nr_compressed_extents);
|
|
prt_printf(out, " compressed size: ");
|
|
prt_human_readable_u64(out, compressed_sectors_compressed << 9);
|
|
prt_printf(out, "\n");
|
|
prt_printf(out, " uncompressed size: ");
|
|
prt_human_readable_u64(out, compressed_sectors_uncompressed << 9);
|
|
prt_printf(out, "\n");
|
|
|
|
prt_printf(out, "incompressible:\n");
|
|
prt_printf(out, " nr extents: %llu\n", nr_incompressible_extents);
|
|
prt_printf(out, " size: ");
|
|
prt_human_readable_u64(out, incompressible_sectors << 9);
|
|
prt_printf(out, "\n");
|
|
return 0;
|
|
}
|
|
|
|
static void bch2_gc_gens_pos_to_text(struct printbuf *out, struct bch_fs *c)
|
|
{
|
|
prt_printf(out, "%s: ", bch2_btree_id_str(c->gc_gens_btree));
|
|
bch2_bpos_to_text(out, c->gc_gens_pos);
|
|
prt_printf(out, "\n");
|
|
}
|
|
|
|
static void bch2_btree_wakeup_all(struct bch_fs *c)
|
|
{
|
|
struct btree_trans *trans;
|
|
|
|
seqmutex_lock(&c->btree_trans_lock);
|
|
list_for_each_entry(trans, &c->btree_trans_list, list) {
|
|
struct btree_bkey_cached_common *b = READ_ONCE(trans->locking);
|
|
|
|
if (b)
|
|
six_lock_wakeup_all(&b->lock);
|
|
|
|
}
|
|
seqmutex_unlock(&c->btree_trans_lock);
|
|
}
|
|
|
|
SHOW(bch2_fs)
|
|
{
|
|
struct bch_fs *c = container_of(kobj, struct bch_fs, kobj);
|
|
|
|
sysfs_print(minor, c->minor);
|
|
sysfs_printf(internal_uuid, "%pU", c->sb.uuid.b);
|
|
|
|
sysfs_hprint(btree_cache_size, bch2_btree_cache_size(c));
|
|
|
|
if (attr == &sysfs_btree_write_stats)
|
|
bch2_btree_write_stats_to_text(out, c);
|
|
|
|
sysfs_printf(btree_gc_periodic, "%u", (int) c->btree_gc_periodic);
|
|
|
|
if (attr == &sysfs_gc_gens_pos)
|
|
bch2_gc_gens_pos_to_text(out, c);
|
|
|
|
sysfs_printf(copy_gc_enabled, "%i", c->copy_gc_enabled);
|
|
|
|
sysfs_printf(rebalance_enabled, "%i", c->rebalance.enabled);
|
|
sysfs_pd_controller_show(rebalance, &c->rebalance.pd); /* XXX */
|
|
|
|
if (attr == &sysfs_copy_gc_wait)
|
|
bch2_copygc_wait_to_text(out, c);
|
|
|
|
if (attr == &sysfs_rebalance_status)
|
|
bch2_rebalance_status_to_text(out, c);
|
|
|
|
sysfs_print(promote_whole_extents, c->promote_whole_extents);
|
|
|
|
/* Debugging: */
|
|
|
|
if (attr == &sysfs_journal_debug)
|
|
bch2_journal_debug_to_text(out, &c->journal);
|
|
|
|
if (attr == &sysfs_btree_updates)
|
|
bch2_btree_updates_to_text(out, c);
|
|
|
|
if (attr == &sysfs_btree_cache)
|
|
bch2_btree_cache_to_text(out, c);
|
|
|
|
if (attr == &sysfs_btree_key_cache)
|
|
bch2_btree_key_cache_to_text(out, &c->btree_key_cache);
|
|
|
|
if (attr == &sysfs_stripes_heap)
|
|
bch2_stripes_heap_to_text(out, c);
|
|
|
|
if (attr == &sysfs_open_buckets)
|
|
bch2_open_buckets_to_text(out, c);
|
|
|
|
if (attr == &sysfs_open_buckets_partial)
|
|
bch2_open_buckets_partial_to_text(out, c);
|
|
|
|
if (attr == &sysfs_write_points)
|
|
bch2_write_points_to_text(out, c);
|
|
|
|
if (attr == &sysfs_compression_stats)
|
|
bch2_compression_stats_to_text(out, c);
|
|
|
|
if (attr == &sysfs_new_stripes)
|
|
bch2_new_stripes_to_text(out, c);
|
|
|
|
if (attr == &sysfs_io_timers_read)
|
|
bch2_io_timers_to_text(out, &c->io_clock[READ]);
|
|
|
|
if (attr == &sysfs_io_timers_write)
|
|
bch2_io_timers_to_text(out, &c->io_clock[WRITE]);
|
|
|
|
if (attr == &sysfs_moving_ctxts)
|
|
bch2_fs_moving_ctxts_to_text(out, c);
|
|
|
|
#ifdef BCH_WRITE_REF_DEBUG
|
|
if (attr == &sysfs_write_refs)
|
|
bch2_write_refs_to_text(out, c);
|
|
#endif
|
|
|
|
if (attr == &sysfs_nocow_lock_table)
|
|
bch2_nocow_locks_to_text(out, &c->nocow_locks);
|
|
|
|
if (attr == &sysfs_disk_groups)
|
|
bch2_disk_groups_to_text(out, c);
|
|
|
|
return 0;
|
|
}
|
|
|
|
STORE(bch2_fs)
|
|
{
|
|
struct bch_fs *c = container_of(kobj, struct bch_fs, kobj);
|
|
|
|
if (attr == &sysfs_btree_gc_periodic) {
|
|
ssize_t ret = strtoul_safe(buf, c->btree_gc_periodic)
|
|
?: (ssize_t) size;
|
|
|
|
wake_up_process(c->gc_thread);
|
|
return ret;
|
|
}
|
|
|
|
if (attr == &sysfs_copy_gc_enabled) {
|
|
ssize_t ret = strtoul_safe(buf, c->copy_gc_enabled)
|
|
?: (ssize_t) size;
|
|
|
|
if (c->copygc_thread)
|
|
wake_up_process(c->copygc_thread);
|
|
return ret;
|
|
}
|
|
|
|
if (attr == &sysfs_rebalance_enabled) {
|
|
ssize_t ret = strtoul_safe(buf, c->rebalance.enabled)
|
|
?: (ssize_t) size;
|
|
|
|
rebalance_wakeup(c);
|
|
return ret;
|
|
}
|
|
|
|
sysfs_pd_controller_store(rebalance, &c->rebalance.pd);
|
|
|
|
sysfs_strtoul(promote_whole_extents, c->promote_whole_extents);
|
|
|
|
/* Debugging: */
|
|
|
|
if (!test_bit(BCH_FS_STARTED, &c->flags))
|
|
return -EPERM;
|
|
|
|
/* Debugging: */
|
|
|
|
if (!test_bit(BCH_FS_RW, &c->flags))
|
|
return -EROFS;
|
|
|
|
if (attr == &sysfs_prune_cache) {
|
|
struct shrink_control sc;
|
|
|
|
sc.gfp_mask = GFP_KERNEL;
|
|
sc.nr_to_scan = strtoul_or_return(buf);
|
|
c->btree_cache.shrink->scan_objects(c->btree_cache.shrink, &sc);
|
|
}
|
|
|
|
if (attr == &sysfs_btree_wakeup)
|
|
bch2_btree_wakeup_all(c);
|
|
|
|
if (attr == &sysfs_trigger_gc) {
|
|
/*
|
|
* Full gc is currently incompatible with btree key cache:
|
|
*/
|
|
#if 0
|
|
down_read(&c->state_lock);
|
|
bch2_gc(c, false, false);
|
|
up_read(&c->state_lock);
|
|
#else
|
|
bch2_gc_gens(c);
|
|
#endif
|
|
}
|
|
|
|
if (attr == &sysfs_trigger_discards)
|
|
bch2_do_discards(c);
|
|
|
|
if (attr == &sysfs_trigger_invalidates)
|
|
bch2_do_invalidates(c);
|
|
|
|
#ifdef CONFIG_BCACHEFS_TESTS
|
|
if (attr == &sysfs_perf_test) {
|
|
char *tmp = kstrdup(buf, GFP_KERNEL), *p = tmp;
|
|
char *test = strsep(&p, " \t\n");
|
|
char *nr_str = strsep(&p, " \t\n");
|
|
char *threads_str = strsep(&p, " \t\n");
|
|
unsigned threads;
|
|
u64 nr;
|
|
int ret = -EINVAL;
|
|
|
|
if (threads_str &&
|
|
!(ret = kstrtouint(threads_str, 10, &threads)) &&
|
|
!(ret = bch2_strtoull_h(nr_str, &nr)))
|
|
ret = bch2_btree_perf_test(c, test, nr, threads);
|
|
kfree(tmp);
|
|
|
|
if (ret)
|
|
size = ret;
|
|
}
|
|
#endif
|
|
return size;
|
|
}
|
|
SYSFS_OPS(bch2_fs);
|
|
|
|
struct attribute *bch2_fs_files[] = {
|
|
&sysfs_minor,
|
|
&sysfs_btree_cache_size,
|
|
&sysfs_btree_write_stats,
|
|
|
|
&sysfs_promote_whole_extents,
|
|
|
|
&sysfs_compression_stats,
|
|
|
|
#ifdef CONFIG_BCACHEFS_TESTS
|
|
&sysfs_perf_test,
|
|
#endif
|
|
NULL
|
|
};
|
|
|
|
/* counters dir */
|
|
|
|
SHOW(bch2_fs_counters)
|
|
{
|
|
struct bch_fs *c = container_of(kobj, struct bch_fs, counters_kobj);
|
|
u64 counter = 0;
|
|
u64 counter_since_mount = 0;
|
|
|
|
printbuf_tabstop_push(out, 32);
|
|
|
|
#define x(t, ...) \
|
|
if (attr == &sysfs_##t) { \
|
|
counter = percpu_u64_get(&c->counters[BCH_COUNTER_##t]);\
|
|
counter_since_mount = counter - c->counters_on_mount[BCH_COUNTER_##t];\
|
|
prt_printf(out, "since mount:"); \
|
|
prt_tab(out); \
|
|
prt_human_readable_u64(out, counter_since_mount); \
|
|
prt_newline(out); \
|
|
\
|
|
prt_printf(out, "since filesystem creation:"); \
|
|
prt_tab(out); \
|
|
prt_human_readable_u64(out, counter); \
|
|
prt_newline(out); \
|
|
}
|
|
BCH_PERSISTENT_COUNTERS()
|
|
#undef x
|
|
return 0;
|
|
}
|
|
|
|
STORE(bch2_fs_counters) {
|
|
return 0;
|
|
}
|
|
|
|
SYSFS_OPS(bch2_fs_counters);
|
|
|
|
struct attribute *bch2_fs_counters_files[] = {
|
|
#define x(t, ...) \
|
|
&sysfs_##t,
|
|
BCH_PERSISTENT_COUNTERS()
|
|
#undef x
|
|
NULL
|
|
};
|
|
/* internal dir - just a wrapper */
|
|
|
|
SHOW(bch2_fs_internal)
|
|
{
|
|
struct bch_fs *c = container_of(kobj, struct bch_fs, internal);
|
|
|
|
return bch2_fs_to_text(out, &c->kobj, attr);
|
|
}
|
|
|
|
STORE(bch2_fs_internal)
|
|
{
|
|
struct bch_fs *c = container_of(kobj, struct bch_fs, internal);
|
|
|
|
return bch2_fs_store(&c->kobj, attr, buf, size);
|
|
}
|
|
SYSFS_OPS(bch2_fs_internal);
|
|
|
|
struct attribute *bch2_fs_internal_files[] = {
|
|
&sysfs_journal_debug,
|
|
&sysfs_btree_updates,
|
|
&sysfs_btree_cache,
|
|
&sysfs_btree_key_cache,
|
|
&sysfs_new_stripes,
|
|
&sysfs_stripes_heap,
|
|
&sysfs_open_buckets,
|
|
&sysfs_open_buckets_partial,
|
|
&sysfs_write_points,
|
|
#ifdef BCH_WRITE_REF_DEBUG
|
|
&sysfs_write_refs,
|
|
#endif
|
|
&sysfs_nocow_lock_table,
|
|
&sysfs_io_timers_read,
|
|
&sysfs_io_timers_write,
|
|
|
|
&sysfs_trigger_gc,
|
|
&sysfs_trigger_discards,
|
|
&sysfs_trigger_invalidates,
|
|
&sysfs_prune_cache,
|
|
&sysfs_btree_wakeup,
|
|
|
|
&sysfs_gc_gens_pos,
|
|
|
|
&sysfs_copy_gc_enabled,
|
|
&sysfs_copy_gc_wait,
|
|
|
|
&sysfs_rebalance_enabled,
|
|
&sysfs_rebalance_status,
|
|
sysfs_pd_controller_files(rebalance),
|
|
|
|
&sysfs_moving_ctxts,
|
|
|
|
&sysfs_internal_uuid,
|
|
|
|
&sysfs_disk_groups,
|
|
NULL
|
|
};
|
|
|
|
/* options */
|
|
|
|
SHOW(bch2_fs_opts_dir)
|
|
{
|
|
struct bch_fs *c = container_of(kobj, struct bch_fs, opts_dir);
|
|
const struct bch_option *opt = container_of(attr, struct bch_option, attr);
|
|
int id = opt - bch2_opt_table;
|
|
u64 v = bch2_opt_get_by_id(&c->opts, id);
|
|
|
|
bch2_opt_to_text(out, c, c->disk_sb.sb, opt, v, OPT_SHOW_FULL_LIST);
|
|
prt_char(out, '\n');
|
|
|
|
return 0;
|
|
}
|
|
|
|
STORE(bch2_fs_opts_dir)
|
|
{
|
|
struct bch_fs *c = container_of(kobj, struct bch_fs, opts_dir);
|
|
const struct bch_option *opt = container_of(attr, struct bch_option, attr);
|
|
int ret, id = opt - bch2_opt_table;
|
|
char *tmp;
|
|
u64 v;
|
|
|
|
/*
|
|
* We don't need to take c->writes for correctness, but it eliminates an
|
|
* unsightly error message in the dmesg log when we're RO:
|
|
*/
|
|
if (unlikely(!bch2_write_ref_tryget(c, BCH_WRITE_REF_sysfs)))
|
|
return -EROFS;
|
|
|
|
tmp = kstrdup(buf, GFP_KERNEL);
|
|
if (!tmp) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
ret = bch2_opt_parse(c, opt, strim(tmp), &v, NULL);
|
|
kfree(tmp);
|
|
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
ret = bch2_opt_check_may_set(c, id, v);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
bch2_opt_set_sb(c, opt, v);
|
|
bch2_opt_set_by_id(&c->opts, id, v);
|
|
|
|
if ((id == Opt_background_target ||
|
|
id == Opt_background_compression) && v)
|
|
bch2_set_rebalance_needs_scan(c, 0);
|
|
|
|
ret = size;
|
|
err:
|
|
bch2_write_ref_put(c, BCH_WRITE_REF_sysfs);
|
|
return ret;
|
|
}
|
|
SYSFS_OPS(bch2_fs_opts_dir);
|
|
|
|
struct attribute *bch2_fs_opts_dir_files[] = { NULL };
|
|
|
|
int bch2_opts_create_sysfs_files(struct kobject *kobj)
|
|
{
|
|
const struct bch_option *i;
|
|
int ret;
|
|
|
|
for (i = bch2_opt_table;
|
|
i < bch2_opt_table + bch2_opts_nr;
|
|
i++) {
|
|
if (!(i->flags & OPT_FS))
|
|
continue;
|
|
|
|
ret = sysfs_create_file(kobj, &i->attr);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* time stats */
|
|
|
|
SHOW(bch2_fs_time_stats)
|
|
{
|
|
struct bch_fs *c = container_of(kobj, struct bch_fs, time_stats);
|
|
|
|
#define x(name) \
|
|
if (attr == &sysfs_time_stat_##name) \
|
|
bch2_time_stats_to_text(out, &c->times[BCH_TIME_##name]);
|
|
BCH_TIME_STATS()
|
|
#undef x
|
|
|
|
return 0;
|
|
}
|
|
|
|
STORE(bch2_fs_time_stats)
|
|
{
|
|
return size;
|
|
}
|
|
SYSFS_OPS(bch2_fs_time_stats);
|
|
|
|
struct attribute *bch2_fs_time_stats_files[] = {
|
|
#define x(name) \
|
|
&sysfs_time_stat_##name,
|
|
BCH_TIME_STATS()
|
|
#undef x
|
|
NULL
|
|
};
|
|
|
|
static void dev_alloc_debug_to_text(struct printbuf *out, struct bch_dev *ca)
|
|
{
|
|
struct bch_fs *c = ca->fs;
|
|
struct bch_dev_usage stats = bch2_dev_usage_read(ca);
|
|
unsigned i, nr[BCH_DATA_NR];
|
|
|
|
memset(nr, 0, sizeof(nr));
|
|
|
|
for (i = 0; i < ARRAY_SIZE(c->open_buckets); i++)
|
|
nr[c->open_buckets[i].data_type]++;
|
|
|
|
printbuf_tabstop_push(out, 8);
|
|
printbuf_tabstop_push(out, 16);
|
|
printbuf_tabstop_push(out, 16);
|
|
printbuf_tabstop_push(out, 16);
|
|
printbuf_tabstop_push(out, 16);
|
|
|
|
prt_tab(out);
|
|
prt_str(out, "buckets");
|
|
prt_tab_rjust(out);
|
|
prt_str(out, "sectors");
|
|
prt_tab_rjust(out);
|
|
prt_str(out, "fragmented");
|
|
prt_tab_rjust(out);
|
|
prt_newline(out);
|
|
|
|
for (i = 0; i < BCH_DATA_NR; i++) {
|
|
prt_str(out, bch2_data_types[i]);
|
|
prt_tab(out);
|
|
prt_u64(out, stats.d[i].buckets);
|
|
prt_tab_rjust(out);
|
|
prt_u64(out, stats.d[i].sectors);
|
|
prt_tab_rjust(out);
|
|
prt_u64(out, stats.d[i].fragmented);
|
|
prt_tab_rjust(out);
|
|
prt_newline(out);
|
|
}
|
|
|
|
prt_str(out, "ec");
|
|
prt_tab(out);
|
|
prt_u64(out, stats.buckets_ec);
|
|
prt_tab_rjust(out);
|
|
prt_newline(out);
|
|
|
|
prt_newline(out);
|
|
|
|
prt_printf(out, "reserves:");
|
|
prt_newline(out);
|
|
for (i = 0; i < BCH_WATERMARK_NR; i++) {
|
|
prt_str(out, bch2_watermarks[i]);
|
|
prt_tab(out);
|
|
prt_u64(out, bch2_dev_buckets_reserved(ca, i));
|
|
prt_tab_rjust(out);
|
|
prt_newline(out);
|
|
}
|
|
|
|
prt_newline(out);
|
|
|
|
printbuf_tabstops_reset(out);
|
|
printbuf_tabstop_push(out, 24);
|
|
|
|
prt_str(out, "freelist_wait");
|
|
prt_tab(out);
|
|
prt_str(out, c->freelist_wait.list.first ? "waiting" : "empty");
|
|
prt_newline(out);
|
|
|
|
prt_str(out, "open buckets allocated");
|
|
prt_tab(out);
|
|
prt_u64(out, OPEN_BUCKETS_COUNT - c->open_buckets_nr_free);
|
|
prt_newline(out);
|
|
|
|
prt_str(out, "open buckets this dev");
|
|
prt_tab(out);
|
|
prt_u64(out, ca->nr_open_buckets);
|
|
prt_newline(out);
|
|
|
|
prt_str(out, "open buckets total");
|
|
prt_tab(out);
|
|
prt_u64(out, OPEN_BUCKETS_COUNT);
|
|
prt_newline(out);
|
|
|
|
prt_str(out, "open_buckets_wait");
|
|
prt_tab(out);
|
|
prt_str(out, c->open_buckets_wait.list.first ? "waiting" : "empty");
|
|
prt_newline(out);
|
|
|
|
prt_str(out, "open_buckets_btree");
|
|
prt_tab(out);
|
|
prt_u64(out, nr[BCH_DATA_btree]);
|
|
prt_newline(out);
|
|
|
|
prt_str(out, "open_buckets_user");
|
|
prt_tab(out);
|
|
prt_u64(out, nr[BCH_DATA_user]);
|
|
prt_newline(out);
|
|
|
|
prt_str(out, "buckets_to_invalidate");
|
|
prt_tab(out);
|
|
prt_u64(out, should_invalidate_buckets(ca, stats));
|
|
prt_newline(out);
|
|
|
|
prt_str(out, "btree reserve cache");
|
|
prt_tab(out);
|
|
prt_u64(out, c->btree_reserve_cache_nr);
|
|
prt_newline(out);
|
|
}
|
|
|
|
static const char * const bch2_rw[] = {
|
|
"read",
|
|
"write",
|
|
NULL
|
|
};
|
|
|
|
static void dev_io_done_to_text(struct printbuf *out, struct bch_dev *ca)
|
|
{
|
|
int rw, i;
|
|
|
|
for (rw = 0; rw < 2; rw++) {
|
|
prt_printf(out, "%s:\n", bch2_rw[rw]);
|
|
|
|
for (i = 1; i < BCH_DATA_NR; i++)
|
|
prt_printf(out, "%-12s:%12llu\n",
|
|
bch2_data_types[i],
|
|
percpu_u64_get(&ca->io_done->sectors[rw][i]) << 9);
|
|
}
|
|
}
|
|
|
|
SHOW(bch2_dev)
|
|
{
|
|
struct bch_dev *ca = container_of(kobj, struct bch_dev, kobj);
|
|
struct bch_fs *c = ca->fs;
|
|
|
|
sysfs_printf(uuid, "%pU\n", ca->uuid.b);
|
|
|
|
sysfs_print(bucket_size, bucket_bytes(ca));
|
|
sysfs_print(first_bucket, ca->mi.first_bucket);
|
|
sysfs_print(nbuckets, ca->mi.nbuckets);
|
|
sysfs_print(durability, ca->mi.durability);
|
|
sysfs_print(discard, ca->mi.discard);
|
|
|
|
if (attr == &sysfs_label) {
|
|
if (ca->mi.group)
|
|
bch2_disk_path_to_text(out, c, ca->mi.group - 1);
|
|
prt_char(out, '\n');
|
|
}
|
|
|
|
if (attr == &sysfs_has_data) {
|
|
prt_bitflags(out, bch2_data_types, bch2_dev_has_data(c, ca));
|
|
prt_char(out, '\n');
|
|
}
|
|
|
|
if (attr == &sysfs_state_rw) {
|
|
prt_string_option(out, bch2_member_states, ca->mi.state);
|
|
prt_char(out, '\n');
|
|
}
|
|
|
|
if (attr == &sysfs_io_done)
|
|
dev_io_done_to_text(out, ca);
|
|
|
|
if (attr == &sysfs_io_errors)
|
|
bch2_dev_io_errors_to_text(out, ca);
|
|
|
|
sysfs_print(io_latency_read, atomic64_read(&ca->cur_latency[READ]));
|
|
sysfs_print(io_latency_write, atomic64_read(&ca->cur_latency[WRITE]));
|
|
|
|
if (attr == &sysfs_io_latency_stats_read)
|
|
bch2_time_stats_to_text(out, &ca->io_latency[READ]);
|
|
|
|
if (attr == &sysfs_io_latency_stats_write)
|
|
bch2_time_stats_to_text(out, &ca->io_latency[WRITE]);
|
|
|
|
sysfs_printf(congested, "%u%%",
|
|
clamp(atomic_read(&ca->congested), 0, CONGESTED_MAX)
|
|
* 100 / CONGESTED_MAX);
|
|
|
|
if (attr == &sysfs_alloc_debug)
|
|
dev_alloc_debug_to_text(out, ca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
STORE(bch2_dev)
|
|
{
|
|
struct bch_dev *ca = container_of(kobj, struct bch_dev, kobj);
|
|
struct bch_fs *c = ca->fs;
|
|
struct bch_member *mi;
|
|
|
|
if (attr == &sysfs_discard) {
|
|
bool v = strtoul_or_return(buf);
|
|
|
|
mutex_lock(&c->sb_lock);
|
|
mi = bch2_members_v2_get_mut(c->disk_sb.sb, ca->dev_idx);
|
|
|
|
if (v != BCH_MEMBER_DISCARD(mi)) {
|
|
SET_BCH_MEMBER_DISCARD(mi, v);
|
|
bch2_write_super(c);
|
|
}
|
|
mutex_unlock(&c->sb_lock);
|
|
}
|
|
|
|
if (attr == &sysfs_durability) {
|
|
u64 v = strtoul_or_return(buf);
|
|
|
|
mutex_lock(&c->sb_lock);
|
|
mi = bch2_members_v2_get_mut(c->disk_sb.sb, ca->dev_idx);
|
|
|
|
if (v + 1 != BCH_MEMBER_DURABILITY(mi)) {
|
|
SET_BCH_MEMBER_DURABILITY(mi, v + 1);
|
|
bch2_write_super(c);
|
|
}
|
|
mutex_unlock(&c->sb_lock);
|
|
}
|
|
|
|
if (attr == &sysfs_label) {
|
|
char *tmp;
|
|
int ret;
|
|
|
|
tmp = kstrdup(buf, GFP_KERNEL);
|
|
if (!tmp)
|
|
return -ENOMEM;
|
|
|
|
ret = bch2_dev_group_set(c, ca, strim(tmp));
|
|
kfree(tmp);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (attr == &sysfs_io_errors_reset)
|
|
bch2_dev_errors_reset(ca);
|
|
|
|
return size;
|
|
}
|
|
SYSFS_OPS(bch2_dev);
|
|
|
|
struct attribute *bch2_dev_files[] = {
|
|
&sysfs_uuid,
|
|
&sysfs_bucket_size,
|
|
&sysfs_first_bucket,
|
|
&sysfs_nbuckets,
|
|
&sysfs_durability,
|
|
|
|
/* settings: */
|
|
&sysfs_discard,
|
|
&sysfs_state_rw,
|
|
&sysfs_label,
|
|
|
|
&sysfs_has_data,
|
|
&sysfs_io_done,
|
|
&sysfs_io_errors,
|
|
&sysfs_io_errors_reset,
|
|
|
|
&sysfs_io_latency_read,
|
|
&sysfs_io_latency_write,
|
|
&sysfs_io_latency_stats_read,
|
|
&sysfs_io_latency_stats_write,
|
|
&sysfs_congested,
|
|
|
|
/* debug: */
|
|
&sysfs_alloc_debug,
|
|
NULL
|
|
};
|
|
|
|
#endif /* _BCACHEFS_SYSFS_H_ */
|