Commit dfb79ddb authored by Dennis Zhou's avatar Dennis Zhou Committed by David Sterba

btrfs: track discardable extents for async discard

The number of discardable extents will serve as the rate limiting metric
for how often we should discard. This keeps track of discardable extents
in the free space caches by maintaining deltas and propagating them to
the global count.

The deltas are calculated from 2 values stored in PREV and CURR entries,
then propagated up to the global discard ctl.  The current counter value
becomes the previous counter value after update.
Signed-off-by: default avatarDennis Zhou <dennis@kernel.org>
Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
[ update changelog ]
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent e4faab84
...@@ -101,6 +101,14 @@ struct btrfs_ref; ...@@ -101,6 +101,14 @@ struct btrfs_ref;
#define BTRFS_MAX_EXTENT_SIZE SZ_128M #define BTRFS_MAX_EXTENT_SIZE SZ_128M
/*
* Deltas are an effective way to populate global statistics. Give macro names
* to make it clear what we're doing. An example is discard_extents in
* btrfs_free_space_ctl.
*/
#define BTRFS_STAT_NR_ENTRIES 2
#define BTRFS_STAT_CURR 0
#define BTRFS_STAT_PREV 1
/* /*
* Count how many BTRFS_MAX_EXTENT_SIZE cover the @size * Count how many BTRFS_MAX_EXTENT_SIZE cover the @size
...@@ -458,6 +466,7 @@ struct btrfs_discard_ctl { ...@@ -458,6 +466,7 @@ struct btrfs_discard_ctl {
spinlock_t lock; spinlock_t lock;
struct btrfs_block_group *block_group; struct btrfs_block_group *block_group;
struct list_head discard_list[BTRFS_NR_DISCARD_LISTS]; struct list_head discard_list[BTRFS_NR_DISCARD_LISTS];
atomic_t discardable_extents;
}; };
/* delayed seq elem */ /* delayed seq elem */
......
...@@ -342,6 +342,35 @@ bool btrfs_run_discard_work(struct btrfs_discard_ctl *discard_ctl) ...@@ -342,6 +342,35 @@ bool btrfs_run_discard_work(struct btrfs_discard_ctl *discard_ctl)
test_bit(BTRFS_FS_DISCARD_RUNNING, &fs_info->flags)); test_bit(BTRFS_FS_DISCARD_RUNNING, &fs_info->flags));
} }
/**
* btrfs_discard_update_discardable - propagate discard counters
* @block_group: block_group of interest
* @ctl: free_space_ctl of @block_group
*
* This propagates deltas of counters up to the discard_ctl. It maintains a
* current counter and a previous counter passing the delta up to the global
* stat. Then the current counter value becomes the previous counter value.
*/
void btrfs_discard_update_discardable(struct btrfs_block_group *block_group,
struct btrfs_free_space_ctl *ctl)
{
struct btrfs_discard_ctl *discard_ctl;
s32 extents_delta;
if (!block_group || !btrfs_test_opt(block_group->fs_info, DISCARD_ASYNC))
return;
discard_ctl = &block_group->fs_info->discard_ctl;
extents_delta = ctl->discardable_extents[BTRFS_STAT_CURR] -
ctl->discardable_extents[BTRFS_STAT_PREV];
if (extents_delta) {
atomic_add(extents_delta, &discard_ctl->discardable_extents);
ctl->discardable_extents[BTRFS_STAT_PREV] =
ctl->discardable_extents[BTRFS_STAT_CURR];
}
}
/** /**
* btrfs_discard_punt_unused_bgs_list - punt unused_bgs list to discard lists * btrfs_discard_punt_unused_bgs_list - punt unused_bgs list to discard lists
* @fs_info: fs_info of interest * @fs_info: fs_info of interest
...@@ -423,6 +452,8 @@ void btrfs_discard_init(struct btrfs_fs_info *fs_info) ...@@ -423,6 +452,8 @@ void btrfs_discard_init(struct btrfs_fs_info *fs_info)
for (i = 0; i < BTRFS_NR_DISCARD_LISTS; i++) for (i = 0; i < BTRFS_NR_DISCARD_LISTS; i++)
INIT_LIST_HEAD(&discard_ctl->discard_list[i]); INIT_LIST_HEAD(&discard_ctl->discard_list[i]);
atomic_set(&discard_ctl->discardable_extents, 0);
} }
void btrfs_discard_cleanup(struct btrfs_fs_info *fs_info) void btrfs_discard_cleanup(struct btrfs_fs_info *fs_info)
......
...@@ -16,6 +16,10 @@ void btrfs_discard_schedule_work(struct btrfs_discard_ctl *discard_ctl, ...@@ -16,6 +16,10 @@ void btrfs_discard_schedule_work(struct btrfs_discard_ctl *discard_ctl,
bool override); bool override);
bool btrfs_run_discard_work(struct btrfs_discard_ctl *discard_ctl); bool btrfs_run_discard_work(struct btrfs_discard_ctl *discard_ctl);
/* Update operations */
void btrfs_discard_update_discardable(struct btrfs_block_group *block_group,
struct btrfs_free_space_ctl *ctl);
/* Setup/cleanup operations */ /* Setup/cleanup operations */
void btrfs_discard_punt_unused_bgs_list(struct btrfs_fs_info *fs_info); void btrfs_discard_punt_unused_bgs_list(struct btrfs_fs_info *fs_info);
void btrfs_discard_resume(struct btrfs_fs_info *fs_info); void btrfs_discard_resume(struct btrfs_fs_info *fs_info);
......
...@@ -32,6 +32,8 @@ struct btrfs_trim_range { ...@@ -32,6 +32,8 @@ struct btrfs_trim_range {
struct list_head list; struct list_head list;
}; };
static int count_bitmap_extents(struct btrfs_free_space_ctl *ctl,
struct btrfs_free_space *bitmap_info);
static int link_free_space(struct btrfs_free_space_ctl *ctl, static int link_free_space(struct btrfs_free_space_ctl *ctl,
struct btrfs_free_space *info); struct btrfs_free_space *info);
static void unlink_free_space(struct btrfs_free_space_ctl *ctl, static void unlink_free_space(struct btrfs_free_space_ctl *ctl,
...@@ -816,12 +818,17 @@ static int __load_free_space_cache(struct btrfs_root *root, struct inode *inode, ...@@ -816,12 +818,17 @@ static int __load_free_space_cache(struct btrfs_root *root, struct inode *inode,
ret = io_ctl_read_bitmap(&io_ctl, e); ret = io_ctl_read_bitmap(&io_ctl, e);
if (ret) if (ret)
goto free_cache; goto free_cache;
e->bitmap_extents = count_bitmap_extents(ctl, e);
if (!btrfs_free_space_trimmed(e))
ctl->discardable_extents[BTRFS_STAT_CURR] +=
e->bitmap_extents;
} }
io_ctl_drop_pages(&io_ctl); io_ctl_drop_pages(&io_ctl);
merge_space_tree(ctl); merge_space_tree(ctl);
ret = 1; ret = 1;
out: out:
btrfs_discard_update_discardable(ctl->private, ctl);
io_ctl_free(&io_ctl); io_ctl_free(&io_ctl);
return ret; return ret;
free_cache: free_cache:
...@@ -1635,6 +1642,9 @@ __unlink_free_space(struct btrfs_free_space_ctl *ctl, ...@@ -1635,6 +1642,9 @@ __unlink_free_space(struct btrfs_free_space_ctl *ctl,
{ {
rb_erase(&info->offset_index, &ctl->free_space_offset); rb_erase(&info->offset_index, &ctl->free_space_offset);
ctl->free_extents--; ctl->free_extents--;
if (!info->bitmap && !btrfs_free_space_trimmed(info))
ctl->discardable_extents[BTRFS_STAT_CURR]--;
} }
static void unlink_free_space(struct btrfs_free_space_ctl *ctl, static void unlink_free_space(struct btrfs_free_space_ctl *ctl,
...@@ -1655,6 +1665,9 @@ static int link_free_space(struct btrfs_free_space_ctl *ctl, ...@@ -1655,6 +1665,9 @@ static int link_free_space(struct btrfs_free_space_ctl *ctl,
if (ret) if (ret)
return ret; return ret;
if (!info->bitmap && !btrfs_free_space_trimmed(info))
ctl->discardable_extents[BTRFS_STAT_CURR]++;
ctl->free_space += info->bytes; ctl->free_space += info->bytes;
ctl->free_extents++; ctl->free_extents++;
return ret; return ret;
...@@ -1711,17 +1724,29 @@ static inline void __bitmap_clear_bits(struct btrfs_free_space_ctl *ctl, ...@@ -1711,17 +1724,29 @@ static inline void __bitmap_clear_bits(struct btrfs_free_space_ctl *ctl,
struct btrfs_free_space *info, struct btrfs_free_space *info,
u64 offset, u64 bytes) u64 offset, u64 bytes)
{ {
unsigned long start, count; unsigned long start, count, end;
int extent_delta = -1;
start = offset_to_bit(info->offset, ctl->unit, offset); start = offset_to_bit(info->offset, ctl->unit, offset);
count = bytes_to_bits(bytes, ctl->unit); count = bytes_to_bits(bytes, ctl->unit);
ASSERT(start + count <= BITS_PER_BITMAP); end = start + count;
ASSERT(end <= BITS_PER_BITMAP);
bitmap_clear(info->bitmap, start, count); bitmap_clear(info->bitmap, start, count);
info->bytes -= bytes; info->bytes -= bytes;
if (info->max_extent_size > ctl->unit) if (info->max_extent_size > ctl->unit)
info->max_extent_size = 0; info->max_extent_size = 0;
if (start && test_bit(start - 1, info->bitmap))
extent_delta++;
if (end < BITS_PER_BITMAP && test_bit(end, info->bitmap))
extent_delta++;
info->bitmap_extents += extent_delta;
if (!btrfs_free_space_trimmed(info))
ctl->discardable_extents[BTRFS_STAT_CURR] += extent_delta;
} }
static void bitmap_clear_bits(struct btrfs_free_space_ctl *ctl, static void bitmap_clear_bits(struct btrfs_free_space_ctl *ctl,
...@@ -1736,16 +1761,28 @@ static void bitmap_set_bits(struct btrfs_free_space_ctl *ctl, ...@@ -1736,16 +1761,28 @@ static void bitmap_set_bits(struct btrfs_free_space_ctl *ctl,
struct btrfs_free_space *info, u64 offset, struct btrfs_free_space *info, u64 offset,
u64 bytes) u64 bytes)
{ {
unsigned long start, count; unsigned long start, count, end;
int extent_delta = 1;
start = offset_to_bit(info->offset, ctl->unit, offset); start = offset_to_bit(info->offset, ctl->unit, offset);
count = bytes_to_bits(bytes, ctl->unit); count = bytes_to_bits(bytes, ctl->unit);
ASSERT(start + count <= BITS_PER_BITMAP); end = start + count;
ASSERT(end <= BITS_PER_BITMAP);
bitmap_set(info->bitmap, start, count); bitmap_set(info->bitmap, start, count);
info->bytes += bytes; info->bytes += bytes;
ctl->free_space += bytes; ctl->free_space += bytes;
if (start && test_bit(start - 1, info->bitmap))
extent_delta--;
if (end < BITS_PER_BITMAP && test_bit(end, info->bitmap))
extent_delta--;
info->bitmap_extents += extent_delta;
if (!btrfs_free_space_trimmed(info))
ctl->discardable_extents[BTRFS_STAT_CURR] += extent_delta;
} }
/* /*
...@@ -1881,11 +1918,35 @@ find_free_space(struct btrfs_free_space_ctl *ctl, u64 *offset, u64 *bytes, ...@@ -1881,11 +1918,35 @@ find_free_space(struct btrfs_free_space_ctl *ctl, u64 *offset, u64 *bytes,
return NULL; return NULL;
} }
static int count_bitmap_extents(struct btrfs_free_space_ctl *ctl,
struct btrfs_free_space *bitmap_info)
{
struct btrfs_block_group *block_group = ctl->private;
u64 bytes = bitmap_info->bytes;
unsigned int rs, re;
int count = 0;
if (!block_group || !bytes)
return count;
bitmap_for_each_set_region(bitmap_info->bitmap, rs, re, 0,
BITS_PER_BITMAP) {
bytes -= (rs - re) * ctl->unit;
count++;
if (!bytes)
break;
}
return count;
}
static void add_new_bitmap(struct btrfs_free_space_ctl *ctl, static void add_new_bitmap(struct btrfs_free_space_ctl *ctl,
struct btrfs_free_space *info, u64 offset) struct btrfs_free_space *info, u64 offset)
{ {
info->offset = offset_to_bitmap(ctl, offset); info->offset = offset_to_bitmap(ctl, offset);
info->bytes = 0; info->bytes = 0;
info->bitmap_extents = 0;
INIT_LIST_HEAD(&info->list); INIT_LIST_HEAD(&info->list);
link_free_space(ctl, info); link_free_space(ctl, info);
ctl->total_bitmaps++; ctl->total_bitmaps++;
...@@ -1991,8 +2052,12 @@ static u64 add_bytes_to_bitmap(struct btrfs_free_space_ctl *ctl, ...@@ -1991,8 +2052,12 @@ static u64 add_bytes_to_bitmap(struct btrfs_free_space_ctl *ctl,
* This is a tradeoff to make bitmap trim state minimal. We mark the * This is a tradeoff to make bitmap trim state minimal. We mark the
* whole bitmap untrimmed if at any point we add untrimmed regions. * whole bitmap untrimmed if at any point we add untrimmed regions.
*/ */
if (trim_state == BTRFS_TRIM_STATE_UNTRIMMED) if (trim_state == BTRFS_TRIM_STATE_UNTRIMMED) {
if (btrfs_free_space_trimmed(info))
ctl->discardable_extents[BTRFS_STAT_CURR] +=
info->bitmap_extents;
info->trim_state = BTRFS_TRIM_STATE_UNTRIMMED; info->trim_state = BTRFS_TRIM_STATE_UNTRIMMED;
}
end = info->offset + (u64)(BITS_PER_BITMAP * ctl->unit); end = info->offset + (u64)(BITS_PER_BITMAP * ctl->unit);
...@@ -2428,6 +2493,7 @@ int __btrfs_add_free_space(struct btrfs_fs_info *fs_info, ...@@ -2428,6 +2493,7 @@ int __btrfs_add_free_space(struct btrfs_fs_info *fs_info,
if (ret) if (ret)
kmem_cache_free(btrfs_free_space_cachep, info); kmem_cache_free(btrfs_free_space_cachep, info);
out: out:
btrfs_discard_update_discardable(block_group, ctl);
spin_unlock(&ctl->tree_lock); spin_unlock(&ctl->tree_lock);
if (ret) { if (ret) {
...@@ -2560,6 +2626,7 @@ int btrfs_remove_free_space(struct btrfs_block_group *block_group, ...@@ -2560,6 +2626,7 @@ int btrfs_remove_free_space(struct btrfs_block_group *block_group,
goto again; goto again;
} }
out_lock: out_lock:
btrfs_discard_update_discardable(block_group, ctl);
spin_unlock(&ctl->tree_lock); spin_unlock(&ctl->tree_lock);
out: out:
return ret; return ret;
...@@ -2645,8 +2712,16 @@ __btrfs_return_cluster_to_free_space( ...@@ -2645,8 +2712,16 @@ __btrfs_return_cluster_to_free_space(
bitmap = (entry->bitmap != NULL); bitmap = (entry->bitmap != NULL);
if (!bitmap) { if (!bitmap) {
/* Merging treats extents as if they were new */
if (!btrfs_free_space_trimmed(entry))
ctl->discardable_extents[BTRFS_STAT_CURR]--;
try_merge_free_space(ctl, entry, false); try_merge_free_space(ctl, entry, false);
steal_from_bitmap(ctl, entry, false); steal_from_bitmap(ctl, entry, false);
/* As we insert directly, update these statistics */
if (!btrfs_free_space_trimmed(entry))
ctl->discardable_extents[BTRFS_STAT_CURR]++;
} }
tree_insert_offset(&ctl->free_space_offset, tree_insert_offset(&ctl->free_space_offset,
entry->offset, &entry->offset_index, bitmap); entry->offset, &entry->offset_index, bitmap);
...@@ -2703,6 +2778,7 @@ void btrfs_remove_free_space_cache(struct btrfs_block_group *block_group) ...@@ -2703,6 +2778,7 @@ void btrfs_remove_free_space_cache(struct btrfs_block_group *block_group)
cond_resched_lock(&ctl->tree_lock); cond_resched_lock(&ctl->tree_lock);
} }
__btrfs_remove_free_space_cache_locked(ctl); __btrfs_remove_free_space_cache_locked(ctl);
btrfs_discard_update_discardable(block_group, ctl);
spin_unlock(&ctl->tree_lock); spin_unlock(&ctl->tree_lock);
} }
...@@ -2777,6 +2853,7 @@ u64 btrfs_find_space_for_alloc(struct btrfs_block_group *block_group, ...@@ -2777,6 +2853,7 @@ u64 btrfs_find_space_for_alloc(struct btrfs_block_group *block_group,
link_free_space(ctl, entry); link_free_space(ctl, entry);
} }
out: out:
btrfs_discard_update_discardable(block_group, ctl);
spin_unlock(&ctl->tree_lock); spin_unlock(&ctl->tree_lock);
if (align_gap_len) if (align_gap_len)
...@@ -2941,6 +3018,8 @@ u64 btrfs_alloc_from_cluster(struct btrfs_block_group *block_group, ...@@ -2941,6 +3018,8 @@ u64 btrfs_alloc_from_cluster(struct btrfs_block_group *block_group,
entry->bitmap); entry->bitmap);
ctl->total_bitmaps--; ctl->total_bitmaps--;
ctl->op->recalc_thresholds(ctl); ctl->op->recalc_thresholds(ctl);
} else if (!btrfs_free_space_trimmed(entry)) {
ctl->discardable_extents[BTRFS_STAT_CURR]--;
} }
kmem_cache_free(btrfs_free_space_cachep, entry); kmem_cache_free(btrfs_free_space_cachep, entry);
} }
...@@ -3435,15 +3514,24 @@ static void reset_trimming_bitmap(struct btrfs_free_space_ctl *ctl, u64 offset) ...@@ -3435,15 +3514,24 @@ static void reset_trimming_bitmap(struct btrfs_free_space_ctl *ctl, u64 offset)
spin_lock(&ctl->tree_lock); spin_lock(&ctl->tree_lock);
entry = tree_search_offset(ctl, offset, 1, 0); entry = tree_search_offset(ctl, offset, 1, 0);
if (entry) if (entry) {
if (btrfs_free_space_trimmed(entry))
ctl->discardable_extents[BTRFS_STAT_CURR] +=
entry->bitmap_extents;
entry->trim_state = BTRFS_TRIM_STATE_UNTRIMMED; entry->trim_state = BTRFS_TRIM_STATE_UNTRIMMED;
}
spin_unlock(&ctl->tree_lock); spin_unlock(&ctl->tree_lock);
} }
static void end_trimming_bitmap(struct btrfs_free_space *entry) static void end_trimming_bitmap(struct btrfs_free_space_ctl *ctl,
struct btrfs_free_space *entry)
{ {
if (btrfs_free_space_trimming_bitmap(entry)) if (btrfs_free_space_trimming_bitmap(entry)) {
entry->trim_state = BTRFS_TRIM_STATE_TRIMMED; entry->trim_state = BTRFS_TRIM_STATE_TRIMMED;
ctl->discardable_extents[BTRFS_STAT_CURR] -=
entry->bitmap_extents;
}
} }
/* /*
...@@ -3501,7 +3589,7 @@ static int trim_bitmaps(struct btrfs_block_group *block_group, ...@@ -3501,7 +3589,7 @@ static int trim_bitmaps(struct btrfs_block_group *block_group,
* if BTRFS_TRIM_STATE_TRIMMED is set on a bitmap. * if BTRFS_TRIM_STATE_TRIMMED is set on a bitmap.
*/ */
if (ret2 && !minlen) if (ret2 && !minlen)
end_trimming_bitmap(entry); end_trimming_bitmap(ctl, entry);
else else
entry->trim_state = BTRFS_TRIM_STATE_UNTRIMMED; entry->trim_state = BTRFS_TRIM_STATE_UNTRIMMED;
spin_unlock(&ctl->tree_lock); spin_unlock(&ctl->tree_lock);
......
...@@ -28,6 +28,7 @@ struct btrfs_free_space { ...@@ -28,6 +28,7 @@ struct btrfs_free_space {
unsigned long *bitmap; unsigned long *bitmap;
struct list_head list; struct list_head list;
enum btrfs_trim_state trim_state; enum btrfs_trim_state trim_state;
s32 bitmap_extents;
}; };
static inline bool btrfs_free_space_trimmed(struct btrfs_free_space *info) static inline bool btrfs_free_space_trimmed(struct btrfs_free_space *info)
...@@ -50,6 +51,7 @@ struct btrfs_free_space_ctl { ...@@ -50,6 +51,7 @@ struct btrfs_free_space_ctl {
int total_bitmaps; int total_bitmaps;
int unit; int unit;
u64 start; u64 start;
s32 discardable_extents[BTRFS_STAT_NR_ENTRIES];
const struct btrfs_free_space_op *op; const struct btrfs_free_space_op *op;
void *private; void *private;
struct mutex cache_writeout_mutex; struct mutex cache_writeout_mutex;
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include <crypto/hash.h> #include <crypto/hash.h>
#include "ctree.h" #include "ctree.h"
#include "discard.h"
#include "disk-io.h" #include "disk-io.h"
#include "transaction.h" #include "transaction.h"
#include "sysfs.h" #include "sysfs.h"
...@@ -341,7 +342,21 @@ static const struct attribute_group btrfs_static_feature_attr_group = { ...@@ -341,7 +342,21 @@ static const struct attribute_group btrfs_static_feature_attr_group = {
/* /*
* Discard statistics and tunables * Discard statistics and tunables
*/ */
#define discard_to_fs_info(_kobj) to_fs_info((_kobj)->parent->parent)
static ssize_t btrfs_discardable_extents_show(struct kobject *kobj,
struct kobj_attribute *a,
char *buf)
{
struct btrfs_fs_info *fs_info = discard_to_fs_info(kobj);
return snprintf(buf, PAGE_SIZE, "%d\n",
atomic_read(&fs_info->discard_ctl.discardable_extents));
}
BTRFS_ATTR(discard, discardable_extents, btrfs_discardable_extents_show);
static const struct attribute *discard_debug_attrs[] = { static const struct attribute *discard_debug_attrs[] = {
BTRFS_ATTR_PTR(discard, discardable_extents),
NULL, NULL,
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment