spdk_dd: Add data and hole management
A logic has been introduced to skip hole in files and bdevs and so to copy only data. For files lseek is used, for bdevs new bdev API spdk_bdev_seek_[data,hole]. Show progress function will display only affected size_unit Signed-off-by: Damiano Cipriani <damiano.cipriani@suse.com> Change-Id: Ide2b0d825267603d45e00872ea719c8f0e82a60c Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/14363 Tested-by: SPDK CI Jenkins <sys_sgci@intel.com> Reviewed-by: Ben Walker <benjamin.walker@intel.com> Reviewed-by: Jim Harris <james.r.harris@intel.com>
This commit is contained in:
parent
552a26d9d9
commit
3e917320b9
@ -34,6 +34,7 @@ struct spdk_dd_opts {
|
||||
int64_t io_unit_count;
|
||||
uint32_t queue_depth;
|
||||
bool aio;
|
||||
bool sparse;
|
||||
};
|
||||
|
||||
static struct spdk_dd_opts g_opts = {
|
||||
@ -56,6 +57,7 @@ struct dd_io {
|
||||
int idx;
|
||||
#endif
|
||||
void *buf;
|
||||
STAILQ_ENTRY(dd_io) link;
|
||||
};
|
||||
|
||||
enum dd_target_type {
|
||||
@ -118,6 +120,7 @@ struct dd_job {
|
||||
|
||||
uint32_t outstanding;
|
||||
uint64_t copy_size;
|
||||
STAILQ_HEAD(, dd_io) seek_queue;
|
||||
|
||||
struct timespec start_time;
|
||||
uint64_t total_bytes;
|
||||
@ -147,7 +150,8 @@ static struct dd_job g_job = {};
|
||||
static int g_error = 0;
|
||||
static bool g_interrupt;
|
||||
|
||||
static void dd_target_populate_buffer(struct dd_io *io);
|
||||
static void dd_target_seek(struct dd_io *io);
|
||||
static void _dd_bdev_seek_hole_done(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg);
|
||||
|
||||
static void
|
||||
dd_cleanup_bdev(struct dd_target io)
|
||||
@ -278,6 +282,37 @@ dd_status_poller(void *ctx)
|
||||
return SPDK_POLLER_BUSY;
|
||||
}
|
||||
|
||||
static void
|
||||
dd_finalize_output(void)
|
||||
{
|
||||
off_t curr_offset;
|
||||
int rc = 0;
|
||||
|
||||
if (g_job.outstanding > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_opts.output_file) {
|
||||
curr_offset = lseek(g_job.output.u.aio.fd, 0, SEEK_END);
|
||||
if (curr_offset == (off_t) -1) {
|
||||
SPDK_ERRLOG("Could not seek output file for finalize: %s\n", strerror(errno));
|
||||
g_error = errno;
|
||||
} else if ((uint64_t)curr_offset < g_job.copy_size + g_job.output.pos) {
|
||||
rc = ftruncate(g_job.output.u.aio.fd, g_job.copy_size + g_job.output.pos);
|
||||
if (rc != 0) {
|
||||
SPDK_ERRLOG("Could not truncate output file for finalize: %s\n", strerror(errno));
|
||||
g_error = errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (g_error == 0) {
|
||||
dd_show_progress(true);
|
||||
printf("\n\n");
|
||||
}
|
||||
dd_exit(g_error);
|
||||
}
|
||||
|
||||
#ifdef SPDK_CONFIG_URING
|
||||
static void
|
||||
dd_uring_submit(struct dd_io *io, struct dd_target *target, uint64_t length, uint64_t offset)
|
||||
@ -306,7 +341,7 @@ _dd_write_bdev_done(struct spdk_bdev_io *bdev_io,
|
||||
assert(g_job.outstanding > 0);
|
||||
g_job.outstanding--;
|
||||
spdk_bdev_free_io(bdev_io);
|
||||
dd_target_populate_buffer(io);
|
||||
dd_target_seek(io);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -454,7 +489,7 @@ dd_target_populate_buffer(struct dd_io *io)
|
||||
int rc = 0;
|
||||
|
||||
io->offset = g_job.input.pos;
|
||||
io->length = spdk_min((uint64_t)g_opts.io_unit_size, g_job.copy_size - read_offset);
|
||||
io->length = spdk_min(io->length, g_job.copy_size - read_offset);
|
||||
|
||||
if (io->length == 0 || g_error != 0 || g_interrupt == true) {
|
||||
if (g_job.outstanding == 0) {
|
||||
@ -512,6 +547,235 @@ dd_target_populate_buffer(struct dd_io *io)
|
||||
}
|
||||
}
|
||||
|
||||
static off_t
|
||||
dd_file_seek_data(void)
|
||||
{
|
||||
off_t next_data_offset = (off_t) -1;
|
||||
|
||||
next_data_offset = lseek(g_job.input.u.aio.fd, g_job.input.pos, SEEK_DATA);
|
||||
|
||||
if (next_data_offset == (off_t) -1) {
|
||||
/* NXIO with SEEK_DATA means there are no more data to read.
|
||||
* But in case of input and output files, we may have to finalize output file
|
||||
* inserting a hole to the end of the file.
|
||||
*/
|
||||
if (errno == ENXIO) {
|
||||
dd_finalize_output();
|
||||
} else if (g_job.outstanding == 0) {
|
||||
SPDK_ERRLOG("Could not seek input file for data: %s\n", strerror(errno));
|
||||
g_error = errno;
|
||||
dd_exit(g_error);
|
||||
}
|
||||
}
|
||||
|
||||
return next_data_offset;
|
||||
}
|
||||
|
||||
static off_t
|
||||
dd_file_seek_hole(void)
|
||||
{
|
||||
off_t next_hole_offset = (off_t) -1;
|
||||
|
||||
next_hole_offset = lseek(g_job.input.u.aio.fd, g_job.input.pos, SEEK_HOLE);
|
||||
|
||||
if (next_hole_offset == (off_t) -1 && g_job.outstanding == 0) {
|
||||
SPDK_ERRLOG("Could not seek input file for hole: %s\n", strerror(errno));
|
||||
g_error = errno;
|
||||
dd_exit(g_error);
|
||||
}
|
||||
|
||||
return next_hole_offset;
|
||||
}
|
||||
|
||||
static void
|
||||
_dd_bdev_seek_data_done(struct spdk_bdev_io *bdev_io,
|
||||
bool success,
|
||||
void *cb_arg)
|
||||
{
|
||||
struct dd_io *io = cb_arg;
|
||||
uint64_t next_data_offset_blocks = UINT64_MAX;
|
||||
struct dd_target *target = &g_job.input;
|
||||
int rc = 0;
|
||||
|
||||
if (g_error != 0 || g_interrupt == true) {
|
||||
STAILQ_REMOVE_HEAD(&g_job.seek_queue, link);
|
||||
if (g_job.outstanding == 0) {
|
||||
if (g_error == 0) {
|
||||
dd_show_progress(true);
|
||||
printf("\n\n");
|
||||
}
|
||||
dd_exit(g_error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(g_job.outstanding > 0);
|
||||
g_job.outstanding--;
|
||||
|
||||
next_data_offset_blocks = spdk_bdev_io_get_seek_offset(bdev_io);
|
||||
spdk_bdev_free_io(bdev_io);
|
||||
|
||||
/* UINT64_MAX means there are no more data to read.
|
||||
* But in case of input and output files, we may have to finalize output file
|
||||
* inserting a hole to the end of the file.
|
||||
*/
|
||||
if (next_data_offset_blocks == UINT64_MAX) {
|
||||
STAILQ_REMOVE_HEAD(&g_job.seek_queue, link);
|
||||
dd_finalize_output();
|
||||
return;
|
||||
}
|
||||
|
||||
g_job.input.pos = next_data_offset_blocks * g_job.input.block_size;
|
||||
|
||||
g_job.outstanding++;
|
||||
rc = spdk_bdev_seek_hole(target->u.bdev.desc, target->u.bdev.ch,
|
||||
g_job.input.pos / g_job.input.block_size,
|
||||
_dd_bdev_seek_hole_done, io);
|
||||
|
||||
if (rc != 0) {
|
||||
SPDK_ERRLOG("%s\n", strerror(-rc));
|
||||
STAILQ_REMOVE_HEAD(&g_job.seek_queue, link);
|
||||
assert(g_job.outstanding > 0);
|
||||
g_job.outstanding--;
|
||||
g_error = rc;
|
||||
if (g_job.outstanding == 0) {
|
||||
dd_exit(rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_dd_bdev_seek_hole_done(struct spdk_bdev_io *bdev_io,
|
||||
bool success,
|
||||
void *cb_arg)
|
||||
{
|
||||
struct dd_io *io = cb_arg;
|
||||
struct dd_target *target = &g_job.input;
|
||||
uint64_t next_hole_offset_blocks = UINT64_MAX;
|
||||
struct dd_io *seek_io;
|
||||
int rc = 0;
|
||||
|
||||
/* First seek operation is the one in progress, i.e. this one just ended */
|
||||
STAILQ_REMOVE_HEAD(&g_job.seek_queue, link);
|
||||
|
||||
if (g_error != 0 || g_interrupt == true) {
|
||||
if (g_job.outstanding == 0) {
|
||||
if (g_error == 0) {
|
||||
dd_show_progress(true);
|
||||
printf("\n\n");
|
||||
}
|
||||
dd_exit(g_error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(g_job.outstanding > 0);
|
||||
g_job.outstanding--;
|
||||
|
||||
next_hole_offset_blocks = spdk_bdev_io_get_seek_offset(bdev_io);
|
||||
spdk_bdev_free_io(bdev_io);
|
||||
|
||||
/* UINT64_MAX means there are no more holes. */
|
||||
if (next_hole_offset_blocks == UINT64_MAX) {
|
||||
io->length = g_opts.io_unit_size;
|
||||
} else {
|
||||
io->length = spdk_min((uint64_t)g_opts.io_unit_size,
|
||||
next_hole_offset_blocks * g_job.input.block_size - g_job.input.pos);
|
||||
}
|
||||
|
||||
dd_target_populate_buffer(io);
|
||||
|
||||
/* If input reading is not at the end, start following seek operation in the queue */
|
||||
if (!STAILQ_EMPTY(&g_job.seek_queue) && g_job.input.pos < g_job.input.total_size) {
|
||||
seek_io = STAILQ_FIRST(&g_job.seek_queue);
|
||||
assert(seek_io != NULL);
|
||||
g_job.outstanding++;
|
||||
rc = spdk_bdev_seek_data(target->u.bdev.desc, target->u.bdev.ch,
|
||||
g_job.input.pos / g_job.input.block_size,
|
||||
_dd_bdev_seek_data_done, seek_io);
|
||||
|
||||
if (rc != 0) {
|
||||
SPDK_ERRLOG("%s\n", strerror(-rc));
|
||||
assert(g_job.outstanding > 0);
|
||||
g_job.outstanding--;
|
||||
g_error = rc;
|
||||
if (g_job.outstanding == 0) {
|
||||
dd_exit(rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dd_target_seek(struct dd_io *io)
|
||||
{
|
||||
struct dd_target *target = &g_job.input;
|
||||
uint64_t read_region_start = g_opts.input_offset * g_opts.io_unit_size;
|
||||
uint64_t read_offset = g_job.input.pos - read_region_start;
|
||||
off_t next_data_offset = (off_t) -1;
|
||||
off_t next_hole_offset = (off_t) -1;
|
||||
int rc = 0;
|
||||
|
||||
if (!g_opts.sparse) {
|
||||
dd_target_populate_buffer(io);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_job.copy_size - read_offset == 0 || g_error != 0 || g_interrupt == true) {
|
||||
if (g_job.outstanding == 0) {
|
||||
if (g_error == 0) {
|
||||
dd_show_progress(true);
|
||||
printf("\n\n");
|
||||
}
|
||||
dd_exit(g_error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (target->type == DD_TARGET_TYPE_FILE) {
|
||||
next_data_offset = dd_file_seek_data();
|
||||
if (next_data_offset < 0) {
|
||||
return;
|
||||
} else if ((uint64_t)next_data_offset > g_job.input.pos) {
|
||||
g_job.input.pos = next_data_offset;
|
||||
}
|
||||
|
||||
next_hole_offset = dd_file_seek_hole();
|
||||
if (next_hole_offset < 0) {
|
||||
return;
|
||||
} else if ((uint64_t)next_hole_offset > g_job.input.pos) {
|
||||
io->length = spdk_min((uint64_t)g_opts.io_unit_size,
|
||||
(uint64_t)(next_hole_offset - g_job.input.pos));
|
||||
} else {
|
||||
io->length = g_opts.io_unit_size;
|
||||
}
|
||||
|
||||
dd_target_populate_buffer(io);
|
||||
} else if (target->type == DD_TARGET_TYPE_BDEV) {
|
||||
/* Check if other seek operation is in progress */
|
||||
if (STAILQ_EMPTY(&g_job.seek_queue)) {
|
||||
g_job.outstanding++;
|
||||
rc = spdk_bdev_seek_data(target->u.bdev.desc, target->u.bdev.ch,
|
||||
g_job.input.pos / g_job.input.block_size,
|
||||
_dd_bdev_seek_data_done, io);
|
||||
|
||||
}
|
||||
|
||||
STAILQ_INSERT_TAIL(&g_job.seek_queue, io, link);
|
||||
}
|
||||
|
||||
if (rc != 0) {
|
||||
SPDK_ERRLOG("%s\n", strerror(-rc));
|
||||
assert(g_job.outstanding > 0);
|
||||
g_job.outstanding--;
|
||||
g_error = rc;
|
||||
if (g_job.outstanding == 0) {
|
||||
dd_exit(rc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dd_complete_poll(struct dd_io *io)
|
||||
{
|
||||
@ -526,7 +790,7 @@ dd_complete_poll(struct dd_io *io)
|
||||
dd_target_write(io);
|
||||
break;
|
||||
case DD_WRITE:
|
||||
dd_target_populate_buffer(io);
|
||||
dd_target_seek(io);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
@ -905,6 +1169,7 @@ dd_run(void *arg1)
|
||||
dd_exit(-ENOMEM);
|
||||
return;
|
||||
}
|
||||
g_job.ios[i].length = (uint64_t)g_opts.io_unit_size;
|
||||
}
|
||||
|
||||
if (g_opts.input_file || g_opts.output_file) {
|
||||
@ -957,8 +1222,10 @@ dd_run(void *arg1)
|
||||
g_job.status_poller = spdk_poller_register(dd_status_poller, NULL,
|
||||
STATUS_POLLER_PERIOD_SEC * SPDK_SEC_TO_USEC);
|
||||
|
||||
STAILQ_INIT(&g_job.seek_queue);
|
||||
|
||||
for (i = 0; i < g_opts.queue_depth; i++) {
|
||||
dd_target_populate_buffer(&g_job.ios[i]);
|
||||
dd_target_seek(&g_job.ios[i]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -976,6 +1243,7 @@ enum dd_cmdline_opts {
|
||||
DD_OPTION_QD,
|
||||
DD_OPTION_COUNT,
|
||||
DD_OPTION_AIO,
|
||||
DD_OPTION_SPARSE,
|
||||
};
|
||||
|
||||
static struct option g_cmdline_opts[] = {
|
||||
@ -1051,6 +1319,12 @@ static struct option g_cmdline_opts[] = {
|
||||
.flag = NULL,
|
||||
.val = DD_OPTION_AIO,
|
||||
},
|
||||
{
|
||||
.name = "sparse",
|
||||
.has_arg = 0,
|
||||
.flag = NULL,
|
||||
.val = DD_OPTION_SPARSE,
|
||||
},
|
||||
{
|
||||
.name = NULL
|
||||
}
|
||||
@ -1072,6 +1346,7 @@ usage(void)
|
||||
printf(" --skip Skip this many I/O units at start of input. (default: 0)\n");
|
||||
printf(" --seek Skip this many I/O units at start of output. (default: 0)\n");
|
||||
printf(" --aio Force usage of AIO. (by default io_uring is used if available)\n");
|
||||
printf(" --sparse Enable hole skipping in input target\n");
|
||||
printf(" Available iflag and oflag values:\n");
|
||||
printf(" append - append mode\n");
|
||||
printf(" direct - use direct I/O for data\n");
|
||||
@ -1124,6 +1399,9 @@ parse_args(int argc, char *argv)
|
||||
case DD_OPTION_AIO:
|
||||
g_opts.aio = true;
|
||||
break;
|
||||
case DD_OPTION_SPARSE:
|
||||
g_opts.sparse = true;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
return 1;
|
||||
|
@ -20,3 +20,4 @@ run_test "spdk_dd_bdev_to_bdev" "$testdir/bdev_to_bdev.sh" "${nvmes[@]}"
|
||||
if ((SPDK_TEST_URING == 1)); then
|
||||
run_test "spdk_dd_uring" "$testdir/uring.sh"
|
||||
fi
|
||||
run_test "spdk_dd_sparse" "$testdir/sparse.sh"
|
||||
|
118
test/dd/sparse.sh
Executable file
118
test/dd/sparse.sh
Executable file
@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
testdir=$(readlink -f "$(dirname "$0")")
|
||||
rootdir=$(readlink -f "$testdir/../../")
|
||||
source "$testdir/common.sh"
|
||||
|
||||
cleanup() {
|
||||
rm $aio_disk
|
||||
rm $file1
|
||||
rm $file2
|
||||
rm $file3
|
||||
}
|
||||
|
||||
prepare() {
|
||||
truncate $aio_disk --size 104857600
|
||||
|
||||
dd if=/dev/zero of=$file1 bs=4M count=1
|
||||
dd if=/dev/zero of=$file1 bs=4M count=1 seek=4
|
||||
dd if=/dev/zero of=$file1 bs=4M count=1 seek=8
|
||||
}
|
||||
|
||||
file_to_file() {
|
||||
local stat1_s stat1_b
|
||||
local stat2_s stat2_b
|
||||
|
||||
local -A method_bdev_aio_create_0=(
|
||||
["filename"]=$aio_disk
|
||||
["name"]=$aio_bdev
|
||||
["block_size"]=4096
|
||||
)
|
||||
|
||||
local -A method_bdev_lvol_create_lvstore_1=(
|
||||
["bdev_name"]=$aio_bdev
|
||||
["lvs_name"]=$lvstore
|
||||
)
|
||||
|
||||
"${DD_APP[@]}" \
|
||||
--if="$file1" \
|
||||
--of="$file2" \
|
||||
--bs=12582912 \
|
||||
--sparse \
|
||||
--json <(gen_conf)
|
||||
|
||||
stat1_s=$(stat --printf='%s' $file1)
|
||||
stat2_s=$(stat --printf='%s' $file2)
|
||||
|
||||
[[ $stat1_s == "$stat2_s" ]]
|
||||
|
||||
stat1_b=$(stat --printf='%b' $file1)
|
||||
stat2_b=$(stat --printf='%b' $file2)
|
||||
|
||||
[[ $stat1_b == "$stat2_b" ]]
|
||||
}
|
||||
|
||||
file_to_bdev() {
|
||||
local -A method_bdev_aio_create_0=(
|
||||
["filename"]=$aio_disk
|
||||
["name"]=$aio_bdev
|
||||
["block_size"]=4096
|
||||
)
|
||||
|
||||
local -A method_bdev_lvol_create_1=(
|
||||
["lvs_name"]=$lvstore
|
||||
["lvol_name"]=$lvol
|
||||
["size"]=37748736
|
||||
["thin_provision"]=true
|
||||
)
|
||||
|
||||
"${DD_APP[@]}" \
|
||||
--if="$file2" \
|
||||
--ob="$lvstore/$lvol" \
|
||||
--bs=12582912 \
|
||||
--sparse \
|
||||
--json <(gen_conf)
|
||||
}
|
||||
|
||||
bdev_to_file() {
|
||||
local stat2_s stat2_b
|
||||
local stat3_s stat3_b
|
||||
|
||||
local -A method_bdev_aio_create_0=(
|
||||
["filename"]=$aio_disk
|
||||
["name"]=$aio_bdev
|
||||
["block_size"]=4096
|
||||
)
|
||||
|
||||
"${DD_APP[@]}" \
|
||||
--ib="$lvstore/$lvol" \
|
||||
--of="$file3" \
|
||||
--bs=12582912 \
|
||||
--sparse \
|
||||
--json <(gen_conf)
|
||||
|
||||
stat2_s=$(stat --printf='%s' $file2)
|
||||
stat3_s=$(stat --printf='%s' $file3)
|
||||
|
||||
[[ $stat2_s == "$stat3_s" ]]
|
||||
|
||||
stat2_b=$(stat --printf='%b' $file2)
|
||||
stat3_b=$(stat --printf='%b' $file3)
|
||||
|
||||
[[ $stat2_b == "$stat3_b" ]]
|
||||
}
|
||||
|
||||
aio_disk="dd_sparse_aio_disk"
|
||||
aio_bdev="dd_aio"
|
||||
file1="file_zero1"
|
||||
file2="file_zero2"
|
||||
file3="file_zero3"
|
||||
lvstore="dd_lvstore"
|
||||
lvol="dd_lvol"
|
||||
|
||||
trap "cleanup" EXIT
|
||||
|
||||
prepare
|
||||
|
||||
run_test "dd_sparse_file_to_file" file_to_file
|
||||
run_test "dd_sparse_file_to_bdev" file_to_bdev
|
||||
run_test "dd_sparse_bdev_to_file" bdev_to_file
|
Loading…
Reference in New Issue
Block a user