Implement offline TRIM support

This commit is contained in:
Vitaliy Filippov
2025-03-14 01:37:16 +03:00
parent 3634f005f1
commit 1d94afbd51
13 changed files with 263 additions and 26 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ project(vitastor)
# vitastor-disk
add_executable(vitastor-disk
disk_tool.cpp disk_simple_offsets.cpp
disk_tool_journal.cpp disk_tool_meta.cpp disk_tool_prepare.cpp disk_tool_resize.cpp
disk_tool_discard.cpp disk_tool_journal.cpp disk_tool_meta.cpp disk_tool_prepare.cpp disk_tool_resize.cpp
disk_tool_resize_auto.cpp disk_tool_udev.cpp disk_tool_utils.cpp disk_tool_upgrade.cpp
../util/crc32c.c ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.cpp ../util/rw_blocking.cpp ../util/allocator.cpp ../util/ringloop.cpp ../blockstore/blockstore_disk.cpp
)
+21
View File
@@ -68,6 +68,8 @@ static const char *help_text =
" --data_device_block 4k Override data device block size\n"
" --meta_device_block 4k Override metadata device block size\n"
" --journal_device_block 4k Override journal device block size\n"
" --discard_on_start 0 TRIM unused data device blocks every OSD start (default off)\n"
" --min_discard_size 1M Minimum TRIM block size\n"
" --json Enable JSON output\n"
" \n"
" immediate_commit setting is automatically derived from \"disable fsync\" options.\n"
@@ -128,6 +130,12 @@ static const char *help_text =
" SIZE may include k/m/g/t suffixes. If any of the new layout parameter\n"
" options are not specified, old values will be used.\n"
"\n"
"vitastor-disk trim <osd_num>|<osd_device> [<osd_num>|<osd_device>...]\n"
" Try to discard unused blocks (SSD TRIM) on the data device of each of the OSD(s).\n"
" May only be used on stopped OSDs. Options:\n"
" --min_discard_size 1M Minimum TRIM block size\n"
" --discard_granularity 0 Override device's discard granularity\n"
"\n"
"vitastor-disk start|stop|restart|enable|disable [--now] <device> [device2 device3 ...]\n"
" Manipulate Vitastor OSDs using systemd by their device paths.\n"
" Commands are passed to systemctl with vitastor-osd@<num> units as arguments.\n"
@@ -428,6 +436,19 @@ int main(int argc, char *argv[])
disk_tool_simple_offsets(self.options, self.json);
return 0;
}
else if (!strcmp(cmd[0], "trim"))
{
if (cmd.size() < 2)
{
fprintf(stderr, "OSD number(s) or device path(s) are required\n");
return 1;
}
for (int i = 1; i < cmd.size(); i++)
{
self.trim_data(cmd[i]);
}
return 0;
}
else if (!strcmp(cmd[0], "udev"))
{
if (cmd.size() != 2)
+3 -2
View File
@@ -86,10 +86,10 @@ struct disk_tool_t
int dump_journal();
void dump_journal_entry(int num, journal_entry *je, bool json);
int process_journal(std::function<int(void*)> block_fn);
int process_journal(std::function<int(void*)> block_fn, bool do_open = true);
int process_journal_block(void *buf, std::function<void(int, journal_entry*)> iter_fn);
int process_meta(std::function<void(blockstore_meta_header_v2_t *)> hdr_fn,
std::function<void(uint64_t, clean_disk_entry*, uint8_t*)> record_fn);
std::function<void(uint64_t, clean_disk_entry*, uint8_t*)> record_fn, bool do_open = true);
int dump_meta();
void dump_meta_header(blockstore_meta_header_v2_t *hdr);
@@ -123,6 +123,7 @@ struct disk_tool_t
int pre_exec_osd(std::string device);
int purge_devices(const std::vector<std::string> & devices);
int clear_osd_superblock(const std::string & dev);
int trim_data(std::string device);
json11::Json read_osd_superblock(std::string device, bool expect_exist = true, bool ignore_nonref = false);
uint32_t write_osd_superblock(std::string device, json11::Json params);
+89
View File
@@ -0,0 +1,89 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#include "disk_tool.h"
#include "str_util.h"
#include "json_util.h"
int disk_tool_t::trim_data(std::string device)
{
int r;
// Parse parameters
if (stoull_full(device))
device = "/dev/vitastor/osd"+device+"-data";
json11::Json sb = read_osd_superblock(device, true, false);
if (sb.is_null())
return 1;
auto sb_params = json_to_string_map(sb["params"].object_items());
if (options["discard_granularity"] != "")
sb_params["discard_granularity"] = options["discard_granularity"];
if (options["min_discard_size"] != "")
sb_params["min_discard_size"] = options["min_discard_size"];
try
{
dsk.parse_config(sb_params);
}
catch (std::exception & e)
{
fprintf(stderr, "Error: %s\n", e.what());
return 1;
}
if (!dsk.discard_granularity && sb["real_data_device"].string_value().substr(0, 5) == "/dev/")
{
auto dg = read_file("/sys/block/"+sb["real_data_device"].string_value().substr(5)+"/queue/discard_granularity", true);
if (dg != "")
dsk.discard_granularity = parse_size(trim(dg));
}
// Open devices
try
{
if (options["io"] != "")
dsk.data_io = dsk.meta_io = dsk.journal_io = options["io"];
dsk.open_data();
dsk.open_meta();
dsk.open_journal();
dsk.calc_lengths();
}
catch (std::exception & e)
{
dsk.close_all();
fprintf(stderr, "Error: %s\n", e.what());
return 1;
}
// Fill allocator
fprintf(stderr, "Reading metadata\n");
data_alloc = new allocator_t(dsk.block_count);
r = process_meta(
[this](blockstore_meta_header_v2_t *hdr) {},
[this](uint64_t block_num, clean_disk_entry *entry, uint8_t *bitmap)
{
data_alloc->set(block_num, true);
},
false
);
if (r != 0)
{
dsk.close_all();
return r;
}
fprintf(stderr, "Reading journal\n");
r = process_journal([this](void *buf)
{
return process_journal_block(buf, [this](int num, journal_entry *je)
{
if (je->type == JE_BIG_WRITE || je->type == JE_BIG_WRITE_INSTANT)
{
data_alloc->set(je->big_write.location / dsk.data_block_size, true);
}
});
}, false);
if (r != 0)
{
dsk.close_all();
return r;
}
// Trim
r = dsk.trim_data(data_alloc);
dsk.close_all();
return r == 0;
}
+18 -7
View File
@@ -119,13 +119,21 @@ int disk_tool_t::dump_journal()
return 0;
}
int disk_tool_t::process_journal(std::function<int(void*)> block_fn)
int disk_tool_t::process_journal(std::function<int(void*)> block_fn, bool do_open)
{
dsk.journal_fd = open(dsk.journal_device.c_str(), (options["io"] == "cached" ? 0 : O_DIRECT) | O_RDONLY);
if (dsk.journal_fd < 0)
if (do_open)
{
fprintf(stderr, "Failed to open journal device %s: %s\n", dsk.journal_device.c_str(), strerror(errno));
return 1;
if (dsk.journal_fd >= 0)
{
fprintf(stderr, "Bug: journal device is already opened\n");
return 1;
}
dsk.journal_fd = open(dsk.journal_device.c_str(), (options["io"] == "cached" ? 0 : O_DIRECT) | O_RDONLY);
if (dsk.journal_fd < 0)
{
fprintf(stderr, "Failed to open journal device %s: %s\n", dsk.journal_device.c_str(), strerror(errno));
return 1;
}
}
void *data = memalign_or_die(MEM_ALIGNMENT, dsk.journal_block_size);
journal_pos = 0;
@@ -170,8 +178,11 @@ int disk_tool_t::process_journal(std::function<int(void*)> block_fn)
break;
}
}
close(dsk.journal_fd);
dsk.journal_fd = -1;
if (do_open)
{
close(dsk.journal_fd);
dsk.journal_fd = -1;
}
free(data);
return r;
}
+28 -11
View File
@@ -7,18 +7,26 @@
#include "json_util.h"
int disk_tool_t::process_meta(std::function<void(blockstore_meta_header_v2_t *)> hdr_fn,
std::function<void(uint64_t, clean_disk_entry*, uint8_t*)> record_fn)
std::function<void(uint64_t, clean_disk_entry*, uint8_t*)> record_fn, bool do_open)
{
if (dsk.meta_block_size % DIRECT_IO_ALIGNMENT)
{
fprintf(stderr, "Invalid metadata block size: is not a multiple of %d\n", DIRECT_IO_ALIGNMENT);
return 1;
}
dsk.meta_fd = open(dsk.meta_device.c_str(), (options["io"] == "cached" ? 0 : O_DIRECT) | O_RDONLY);
if (dsk.meta_fd < 0)
if (do_open)
{
fprintf(stderr, "Failed to open metadata device %s: %s\n", dsk.meta_device.c_str(), strerror(errno));
return 1;
if (dsk.meta_fd >= 0)
{
fprintf(stderr, "Bug: Metadata device is already opened\n");
return 1;
}
dsk.meta_fd = open(dsk.meta_device.c_str(), (options["io"] == "cached" ? 0 : O_DIRECT) | O_RDONLY);
if (dsk.meta_fd < 0)
{
fprintf(stderr, "Failed to open metadata device %s: %s\n", dsk.meta_device.c_str(), strerror(errno));
return 1;
}
}
int buf_size = 1024*1024;
if (buf_size % dsk.meta_block_size)
@@ -47,8 +55,11 @@ int disk_tool_t::process_meta(std::function<void(blockstore_meta_header_v2_t *)>
{
fprintf(stderr, "I don't know checksum format %u, the only supported format is crc32c = %u.\n", hdr->data_csum_type, BLOCKSTORE_CSUM_CRC32C);
free(data);
close(dsk.meta_fd);
dsk.meta_fd = -1;
if (do_open)
{
close(dsk.meta_fd);
dsk.meta_fd = -1;
}
return 1;
}
}
@@ -57,8 +68,11 @@ int disk_tool_t::process_meta(std::function<void(blockstore_meta_header_v2_t *)>
// Unsupported version
fprintf(stderr, "Metadata format is too new for me (stored version is %ju, max supported %u).\n", hdr->version, BLOCKSTORE_META_FORMAT_V2);
free(data);
close(dsk.meta_fd);
dsk.meta_fd = -1;
if (do_open)
{
close(dsk.meta_fd);
dsk.meta_fd = -1;
}
return 1;
}
if (hdr->meta_block_size != dsk.meta_block_size)
@@ -145,8 +159,11 @@ int disk_tool_t::process_meta(std::function<void(blockstore_meta_header_v2_t *)>
}
}
free(data);
close(dsk.meta_fd);
dsk.meta_fd = -1;
if (do_open)
{
close(dsk.meta_fd);
dsk.meta_fd = -1;
}
return 0;
}
+3
View File
@@ -26,6 +26,9 @@ int disk_tool_t::prepare_one(std::map<std::string, std::string> options, int is_
"throttle_target_mbs",
"throttle_target_parallelism",
"throttle_threshold_us",
"discard_on_start",
"min_discard_size",
"discard_granularity",
};
if (options.find("force") == options.end())
{
+1 -1
View File
@@ -25,7 +25,7 @@ int disk_tool_t::raw_resize()
r = resize_parse_params();
if (r != 0)
return r;
// Check parameters and fill allocator
// Fill allocator
fprintf(stderr, "Reading metadata\n");
data_alloc = new allocator_t((new_data_len < dsk.data_len ? dsk.data_len : new_data_len) / dsk.data_block_size);
r = process_meta(