Richard W.M. Jones
2021-Jul-26 17:28 UTC
[Libguestfs] [PATCH nbdkit 0/7] Miscellaneous virt-v2v changes to cache and cow filters
I've spent some time today trying to optimize modular virt-v2v a bit, and these are some suggested changes to nbdkit which should help. Rich.
Richard W.M. Jones
2021-Jul-26 17:28 UTC
[Libguestfs] [PATCH nbdkit 1/7] cache: Reduce verbosity of debugging
The cache filter is very verbose in its debugging. Reduce the default level. Use -D cache.verbose=1 to restore original debugging. Compare commit 745a0f13662031c2b9c9b69f62b4ae3a6b2f38f0. --- filters/cache/blk.c | 53 +++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/filters/cache/blk.c b/filters/cache/blk.c index 12e8407e..f52f30e3 100644 --- a/filters/cache/blk.c +++ b/filters/cache/blk.c @@ -93,6 +93,9 @@ enum bm_entry { BLOCK_DIRTY = 3, }; +/* Extra debugging (-D cache.verbose=1). */ +NBDKIT_DLL_PUBLIC int cache_debug_verbose = 0; + int blk_init (void) { @@ -199,12 +202,14 @@ blk_read (nbdkit_next *next, reclaim (fd, &bm); - nbdkit_debug ("cache: blk_read block %" PRIu64 " (offset %" PRIu64 ") is %s", - blknum, (uint64_t) offset, - state == BLOCK_NOT_CACHED ? "not cached" : - state == BLOCK_CLEAN ? "clean" : - state == BLOCK_DIRTY ? "dirty" : - "unknown"); + if (cache_debug_verbose) + nbdkit_debug ("cache: blk_read block %" PRIu64 + " (offset %" PRIu64 ") is %s", + blknum, (uint64_t) offset, + state == BLOCK_NOT_CACHED ? "not cached" : + state == BLOCK_CLEAN ? "clean" : + state == BLOCK_DIRTY ? "dirty" : + "unknown"); if (state == BLOCK_NOT_CACHED) { /* Read underlying plugin. */ unsigned n = blksize, tail = 0; @@ -225,9 +230,10 @@ blk_read (nbdkit_next *next, /* If cache-on-read, copy the block to the cache. */ if (cache_on_read) { - nbdkit_debug ("cache: cache-on-read block %" PRIu64 - " (offset %" PRIu64 ")", - blknum, (uint64_t) offset); + if (cache_debug_verbose) + nbdkit_debug ("cache: cache-on-read block %" PRIu64 + " (offset %" PRIu64 ")", + blknum, (uint64_t) offset); if (pwrite (fd, block, blksize, offset) == -1) { *err = errno; @@ -259,12 +265,14 @@ blk_cache (nbdkit_next *next, reclaim (fd, &bm); - nbdkit_debug ("cache: blk_cache block %" PRIu64 " (offset %" PRIu64 ") is %s", - blknum, (uint64_t) offset, - state == BLOCK_NOT_CACHED ? "not cached" : - state == BLOCK_CLEAN ? "clean" : - state == BLOCK_DIRTY ? "dirty" : - "unknown"); + if (cache_debug_verbose) + nbdkit_debug ("cache: blk_cache block %" PRIu64 + " (offset %" PRIu64 ") is %s", + blknum, (uint64_t) offset, + state == BLOCK_NOT_CACHED ? "not cached" : + state == BLOCK_CLEAN ? "clean" : + state == BLOCK_DIRTY ? "dirty" : + "unknown"); if (state == BLOCK_NOT_CACHED) { /* Read underlying plugin, copy to cache regardless of cache-on-read. */ @@ -284,8 +292,9 @@ blk_cache (nbdkit_next *next, */ memset (block + n, 0, tail); - nbdkit_debug ("cache: cache block %" PRIu64 " (offset %" PRIu64 ")", - blknum, (uint64_t) offset); + if (cache_debug_verbose) + nbdkit_debug ("cache: cache block %" PRIu64 " (offset %" PRIu64 ")", + blknum, (uint64_t) offset); if (pwrite (fd, block, blksize, offset) == -1) { *err = errno; @@ -324,8 +333,9 @@ blk_writethrough (nbdkit_next *next, reclaim (fd, &bm); - nbdkit_debug ("cache: writethrough block %" PRIu64 " (offset %" PRIu64 ")", - blknum, (uint64_t) offset); + if (cache_debug_verbose) + nbdkit_debug ("cache: writethrough block %" PRIu64 " (offset %" PRIu64 ")", + blknum, (uint64_t) offset); if (pwrite (fd, block, blksize, offset) == -1) { *err = errno; @@ -357,8 +367,9 @@ blk_write (nbdkit_next *next, reclaim (fd, &bm); - nbdkit_debug ("cache: writeback block %" PRIu64 " (offset %" PRIu64 ")", - blknum, (uint64_t) offset); + if (cache_debug_verbose) + nbdkit_debug ("cache: writeback block %" PRIu64 " (offset %" PRIu64 ")", + blknum, (uint64_t) offset); if (pwrite (fd, block, blksize, offset) == -1) { *err = errno; -- 2.32.0
Richard W.M. Jones
2021-Jul-26 17:28 UTC
[Libguestfs] [PATCH nbdkit 2/7] cache, cow: Add blk_read_multiple function
Currently the cache and cow filters break up large requests into many single block-sized requests to the underlying plugin. For some plugins (eg. curl) this is very inefficient and causes huge slow-downs. For example I tested nbdkit + curl alone versus nbdkit + cache + curl against a slow, remote VMware server. A simple run of virt-inspector was at least 6-7 times slower. (It was so slow that I didn't actually let it run to completion - I am estimating the slowdown multiple using interim debug messages). As part of fixing this, introduce a blk_read_multiple primitive. At the moment the implementation is simply a loop that calls blk_read so this won't make any performance difference yet. This change is just refactoring. --- filters/cache/blk.h | 6 ++++++ filters/cow/blk.h | 6 ++++++ filters/cache/blk.c | 16 ++++++++++++++++ filters/cache/cache.c | 21 ++++++++------------- filters/cow/blk.c | 20 ++++++++++++++++++-- filters/cow/cow.c | 21 ++++++++------------- 6 files changed, 62 insertions(+), 28 deletions(-) diff --git a/filters/cache/blk.h b/filters/cache/blk.h index 87c753e2..1ee33ed7 100644 --- a/filters/cache/blk.h +++ b/filters/cache/blk.h @@ -55,6 +55,12 @@ extern int blk_read (nbdkit_next *next, uint64_t blknum, uint8_t *block, int *err) __attribute__((__nonnull__ (1, 3, 4))); +/* As above, but read multiple blocks. */ +extern int blk_read_multiple (nbdkit_next *next, + uint64_t blknum, uint64_t nrblocks, + uint8_t *block, int *err) + __attribute__((__nonnull__ (1, 4, 5))); + /* If a single block is not cached, copy it from the plugin. */ extern int blk_cache (nbdkit_next *next, uint64_t blknum, uint8_t *block, int *err) diff --git a/filters/cow/blk.h b/filters/cow/blk.h index e6fd7417..b066c602 100644 --- a/filters/cow/blk.h +++ b/filters/cow/blk.h @@ -55,6 +55,12 @@ extern int blk_read (nbdkit_next *next, uint64_t blknum, uint8_t *block, int *err) __attribute__((__nonnull__ (1, 3, 4))); +/* Read multiple blocks from the overlay or plugin. */ +extern int blk_read_multiple (nbdkit_next *next, + uint64_t blknum, uint64_t nrblocks, + uint8_t *block, int *err) + __attribute__((__nonnull__ (1, 4, 5))); + /* Cache mode for blocks not already in overlay */ enum cache_mode { BLK_CACHE_IGNORE, /* Do nothing */ diff --git a/filters/cache/blk.c b/filters/cache/blk.c index f52f30e3..0d15411b 100644 --- a/filters/cache/blk.c +++ b/filters/cache/blk.c @@ -256,6 +256,22 @@ blk_read (nbdkit_next *next, } } +int +blk_read_multiple (nbdkit_next *next, + uint64_t blknum, uint64_t nrblocks, + uint8_t *block, int *err) +{ + while (nrblocks > 0) { + if (blk_read (next, blknum, block, err) == -1) + return -1; + blknum++; + nrblocks--; + block += blksize; + } + + return 0; +} + int blk_cache (nbdkit_next *next, uint64_t blknum, uint8_t *block, int *err) diff --git a/filters/cache/cache.c b/filters/cache/cache.c index 499aec68..9c081948 100644 --- a/filters/cache/cache.c +++ b/filters/cache/cache.c @@ -313,7 +313,7 @@ cache_pread (nbdkit_next *next, uint32_t flags, int *err) { CLEANUP_FREE uint8_t *block = NULL; - uint64_t blknum, blkoffs; + uint64_t blknum, blkoffs, nrblocks; int r; assert (!flags); @@ -348,22 +348,17 @@ cache_pread (nbdkit_next *next, } /* Aligned body */ - /* XXX This breaks up large read requests into smaller ones, which - * is a problem for plugins which have a large, fixed per-request - * overhead (hello, curl). We should try to keep large requests - * together as much as possible, but that requires us to be much - * smarter here. - */ - while (count >= blksize) { + nrblocks = count / blksize; + if (nrblocks > 0) { ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); - r = blk_read (next, blknum, buf, err); + r = blk_read_multiple (next, blknum, nrblocks, buf, err); if (r == -1) return -1; - buf += blksize; - count -= blksize; - offset += blksize; - blknum++; + buf += nrblocks * blksize; + count -= nrblocks * blksize; + offset += nrblocks * blksize; + blknum += nrblocks; } /* Unaligned tail */ diff --git a/filters/cow/blk.c b/filters/cow/blk.c index 0f12d510..9d6e3e67 100644 --- a/filters/cow/blk.c +++ b/filters/cow/blk.c @@ -223,8 +223,8 @@ blk_status (uint64_t blknum, bool *present, bool *trimmed) *trimmed = state == BLOCK_TRIMMED; } -/* These are the block operations. They always read or write a single - * whole block of size ?blksize?. +/* These are the block operations. They always read or write whole + * blocks of size ?blksize?. */ int blk_read (nbdkit_next *next, @@ -280,6 +280,22 @@ blk_read (nbdkit_next *next, } } +int +blk_read_multiple (nbdkit_next *next, + uint64_t blknum, uint64_t nrblocks, + uint8_t *block, int *err) +{ + while (nrblocks > 0) { + if (blk_read (next, blknum, block, err) == -1) + return -1; + blknum++; + nrblocks--; + block += BLKSIZE; + } + + return 0; +} + int blk_cache (nbdkit_next *next, uint64_t blknum, uint8_t *block, enum cache_mode mode, int *err) diff --git a/filters/cow/cow.c b/filters/cow/cow.c index 3bd09399..f74c0a34 100644 --- a/filters/cow/cow.c +++ b/filters/cow/cow.c @@ -210,7 +210,7 @@ cow_pread (nbdkit_next *next, uint32_t flags, int *err) { CLEANUP_FREE uint8_t *block = NULL; - uint64_t blknum, blkoffs; + uint64_t blknum, blkoffs, nrblocks; int r; if (!IS_ALIGNED (count | offset, BLKSIZE)) { @@ -243,21 +243,16 @@ cow_pread (nbdkit_next *next, } /* Aligned body */ - /* XXX This breaks up large read requests into smaller ones, which - * is a problem for plugins which have a large, fixed per-request - * overhead (hello, curl). We should try to keep large requests - * together as much as possible, but that requires us to be much - * smarter here. - */ - while (count >= BLKSIZE) { - r = blk_read (next, blknum, buf, err); + nrblocks = count / BLKSIZE; + if (nrblocks > 0) { + r = blk_read_multiple (next, blknum, nrblocks, buf, err); if (r == -1) return -1; - buf += BLKSIZE; - count -= BLKSIZE; - offset += BLKSIZE; - blknum++; + buf += nrblocks * BLKSIZE; + count -= nrblocks * BLKSIZE; + offset += nrblocks * BLKSIZE; + blknum += nrblocks; } /* Unaligned tail */ -- 2.32.0
Richard W.M. Jones
2021-Jul-26 17:28 UTC
[Libguestfs] [PATCH nbdkit 3/7] cache, cow: Implement blk_read_multiple efficiently
Continuing from the previous commit, reimplement blk_read_multiple so that it does not break up "runs" of blocks which all have the same cache state. --- filters/cache/blk.c | 83 ++++++++++++++++++++++++++++++--------------- filters/cow/blk.c | 67 ++++++++++++++++++++++-------------- 2 files changed, 96 insertions(+), 54 deletions(-) diff --git a/filters/cache/blk.c b/filters/cache/blk.c index 0d15411b..f85ada35 100644 --- a/filters/cache/blk.c +++ b/filters/cache/blk.c @@ -44,6 +44,7 @@ #include <string.h> #include <unistd.h> #include <fcntl.h> +#include <limits.h> #include <errno.h> #ifdef HAVE_SYS_STATVFS_H @@ -193,26 +194,40 @@ blk_set_size (uint64_t new_size) return 0; } -int -blk_read (nbdkit_next *next, - uint64_t blknum, uint8_t *block, int *err) +static int +_blk_read_multiple (nbdkit_next *next, + uint64_t blknum, uint64_t nrblocks, + uint8_t *block, int *err) { off_t offset = blknum * blksize; - enum bm_entry state = bitmap_get_blk (&bm, blknum, BLOCK_NOT_CACHED); + bool not_cached + bitmap_get_blk (&bm, blknum, BLOCK_NOT_CACHED) == BLOCK_NOT_CACHED; + uint64_t b, runblocks; - reclaim (fd, &bm); + assert (nrblocks > 0); if (cache_debug_verbose) - nbdkit_debug ("cache: blk_read block %" PRIu64 + nbdkit_debug ("cache: blk_read_multiple block %" PRIu64 " (offset %" PRIu64 ") is %s", blknum, (uint64_t) offset, - state == BLOCK_NOT_CACHED ? "not cached" : - state == BLOCK_CLEAN ? "clean" : - state == BLOCK_DIRTY ? "dirty" : - "unknown"); + not_cached ? "not cached" : "cached"); - if (state == BLOCK_NOT_CACHED) { /* Read underlying plugin. */ - unsigned n = blksize, tail = 0; + /* Find out how many of the following blocks form a "run" with the + * same cached/not-cached state. We can process that many blocks in + * one go. + */ + for (b = 1, runblocks = 1; b < nrblocks; ++b, ++runblocks) { + bool s + bitmap_get_blk (&bm, blknum + b, BLOCK_NOT_CACHED) == BLOCK_NOT_CACHED; + if (not_cached != s) + break; + } + + if (not_cached) { /* Read underlying plugin. */ + unsigned n, tail = 0; + + assert (blksize * runblocks <= UINT_MAX); + n = blksize * runblocks; if (offset + n > size) { tail = offset + n - size; @@ -228,32 +243,44 @@ blk_read (nbdkit_next *next, */ memset (block + n, 0, tail); - /* If cache-on-read, copy the block to the cache. */ + /* If cache-on-read, copy the blocks to the cache. */ if (cache_on_read) { if (cache_debug_verbose) nbdkit_debug ("cache: cache-on-read block %" PRIu64 " (offset %" PRIu64 ")", blknum, (uint64_t) offset); - if (pwrite (fd, block, blksize, offset) == -1) { + if (pwrite (fd, block, blksize * runblocks, offset) == -1) { *err = errno; nbdkit_error ("pwrite: %m"); return -1; } - bitmap_set_blk (&bm, blknum, BLOCK_CLEAN); - lru_set_recently_accessed (blknum); + for (b = 0; b < runblocks; ++b) { + bitmap_set_blk (&bm, blknum + b, BLOCK_CLEAN); + lru_set_recently_accessed (blknum + b); + } } - return 0; } else { /* Read cache. */ - if (pread (fd, block, blksize, offset) == -1) { + if (pread (fd, block, blksize * runblocks, offset) == -1) { *err = errno; nbdkit_error ("pread: %m"); return -1; } - lru_set_recently_accessed (blknum); - return 0; + for (b = 0; b < runblocks; ++b) + lru_set_recently_accessed (blknum + b); } + + /* If all done, return. */ + if (runblocks == nrblocks) + return 0; + + /* Recurse to read remaining blocks. */ + return _blk_read_multiple (next, + blknum + runblocks, + nrblocks - runblocks, + block + blksize * runblocks, + err); } int @@ -261,15 +288,15 @@ blk_read_multiple (nbdkit_next *next, uint64_t blknum, uint64_t nrblocks, uint8_t *block, int *err) { - while (nrblocks > 0) { - if (blk_read (next, blknum, block, err) == -1) - return -1; - blknum++; - nrblocks--; - block += blksize; - } + reclaim (fd, &bm); + return _blk_read_multiple (next, blknum, nrblocks, block, err); +} - return 0; +int +blk_read (nbdkit_next *next, + uint64_t blknum, uint8_t *block, int *err) +{ + return blk_read_multiple (next, blknum, 1, block, err); } int diff --git a/filters/cow/blk.c b/filters/cow/blk.c index 9d6e3e67..9e6c8879 100644 --- a/filters/cow/blk.c +++ b/filters/cow/blk.c @@ -79,6 +79,7 @@ #include <inttypes.h> #include <unistd.h> #include <fcntl.h> +#include <limits.h> #include <errno.h> #include <sys/types.h> @@ -227,29 +228,44 @@ blk_status (uint64_t blknum, bool *present, bool *trimmed) * blocks of size ?blksize?. */ int -blk_read (nbdkit_next *next, - uint64_t blknum, uint8_t *block, int *err) +blk_read_multiple (nbdkit_next *next, + uint64_t blknum, uint64_t nrblocks, + uint8_t *block, int *err) { off_t offset = blknum * BLKSIZE; enum bm_entry state; + uint64_t b, runblocks; - /* The state might be modified from another thread - for example - * another thread might write (BLOCK_NOT_ALLOCATED -> - * BLOCK_ALLOCATED) while we are reading from the plugin, returning - * the old data. However a read issued after the write returns - * should always return the correct data. + /* Find out how many of the following blocks form a "run" with the + * same state. We can process that many blocks in one go. + * + * About the locking: The state might be modified from another + * thread - for example another thread might write + * (BLOCK_NOT_ALLOCATED -> BLOCK_ALLOCATED) while we are reading + * from the plugin, returning the old data. However a read issued + * after the write returns should always return the correct data. */ { ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); state = bitmap_get_blk (&bm, blknum, BLOCK_NOT_ALLOCATED); + + for (b = 1, runblocks = 1; b < nrblocks; ++b, ++runblocks) { + enum bm_entry s = bitmap_get_blk (&bm, blknum + b, BLOCK_NOT_ALLOCATED); + if (state != s) + break; + } } if (cow_debug_verbose) - nbdkit_debug ("cow: blk_read block %" PRIu64 " (offset %" PRIu64 ") is %s", + nbdkit_debug ("cow: blk_read_multiple block %" PRIu64 + " (offset %" PRIu64 ") is %s", blknum, (uint64_t) offset, state_to_string (state)); if (state == BLOCK_NOT_ALLOCATED) { /* Read underlying plugin. */ - unsigned n = BLKSIZE, tail = 0; + unsigned n, tail = 0; + + assert (BLKSIZE * runblocks <= UINT_MAX); + n = BLKSIZE * runblocks; if (offset + n > size) { tail = offset + n - size; @@ -264,36 +280,35 @@ blk_read (nbdkit_next *next, * zeroing the tail. */ memset (block + n, 0, tail); - return 0; } else if (state == BLOCK_ALLOCATED) { /* Read overlay. */ - if (pread (fd, block, BLKSIZE, offset) == -1) { + if (pread (fd, block, BLKSIZE * runblocks, offset) == -1) { *err = errno; nbdkit_error ("pread: %m"); return -1; } - return 0; } else /* state == BLOCK_TRIMMED */ { - memset (block, 0, BLKSIZE); + memset (block, 0, BLKSIZE * runblocks); + } + + /* If all done, return. */ + if (runblocks == nrblocks) return 0; - } + + /* Recurse to read remaining blocks. */ + return blk_read_multiple (next, + blknum + runblocks, + nrblocks - runblocks, + block + BLKSIZE * runblocks, + err); } int -blk_read_multiple (nbdkit_next *next, - uint64_t blknum, uint64_t nrblocks, - uint8_t *block, int *err) +blk_read (nbdkit_next *next, + uint64_t blknum, uint8_t *block, int *err) { - while (nrblocks > 0) { - if (blk_read (next, blknum, block, err) == -1) - return -1; - blknum++; - nrblocks--; - block += BLKSIZE; - } - - return 0; + return blk_read_multiple (next, blknum, 1, block, err); } int -- 2.32.0
Richard W.M. Jones
2021-Jul-26 17:28 UTC
[Libguestfs] [PATCH nbdkit 4/7] cache, cow: Use full pread/pwrite operations
Although it probably cannot happen on Linux, POSIX allows pread/pwrite to return or write fewer bytes than requested. The cache and cow filters didn't handle this situation. Replace the raw pread(2)/pwrite(2) syscalls with alternate versions which can handle this. --- common/utils/Makefile.am | 1 + common/utils/utils.h | 2 + common/utils/full-rw.c | 81 ++++++++++++++++++++++++++++++++++++++++ filters/cache/blk.c | 10 ++--- filters/cow/blk.c | 6 +-- 5 files changed, 92 insertions(+), 8 deletions(-) diff --git a/common/utils/Makefile.am b/common/utils/Makefile.am index 1708a4c8..14e9dfc4 100644 --- a/common/utils/Makefile.am +++ b/common/utils/Makefile.am @@ -40,6 +40,7 @@ libutils_la_SOURCES = \ cleanup-nbdkit.c \ cleanup.h \ environ.c \ + full-rw.c \ quote.c \ utils.c \ utils.h \ diff --git a/common/utils/utils.h b/common/utils/utils.h index f8f70212..83397ae1 100644 --- a/common/utils/utils.h +++ b/common/utils/utils.h @@ -40,5 +40,7 @@ extern int set_cloexec (int fd); extern int set_nonblock (int fd); extern char **copy_environ (char **env, ...) __attribute__((__sentinel__)); extern char *make_temporary_directory (void); +extern ssize_t full_pread (int fd, void *buf, size_t count, off_t offset); +extern ssize_t full_pwrite (int fd, const void *buf, size_t count, off_t offset); #endif /* NBDKIT_UTILS_H */ diff --git a/common/utils/full-rw.c b/common/utils/full-rw.c new file mode 100644 index 00000000..55b32cdd --- /dev/null +++ b/common/utils/full-rw.c @@ -0,0 +1,81 @@ +/* nbdkit + * Copyright (C) 2021 Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Red Hat nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* These functions are like pread(2)/pwrite(2) but they always read or + * write the full amount, or fail. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> + +ssize_t +full_pread (int fd, void *buf, size_t count, off_t offset) +{ + ssize_t ret = 0, r; + + while (count > 0) { + r = pread (fd, buf, count, offset); + if (r == -1) return -1; + if (r == 0) { + /* Presumably the caller wasn't expecting end-of-file here, so + * return an error. + */ + errno = EIO; + return -1; + } + ret += r; + offset += r; + count -= r; + } + + return ret; +} + +ssize_t +full_pwrite (int fd, const void *buf, size_t count, off_t offset) +{ + ssize_t ret = 0, r; + + while (count > 0) { + r = pwrite (fd, buf, count, offset); + if (r == -1) return -1; + ret += r; + offset += r; + count -= r; + } + + return ret; +} diff --git a/filters/cache/blk.c b/filters/cache/blk.c index f85ada35..42bd3779 100644 --- a/filters/cache/blk.c +++ b/filters/cache/blk.c @@ -250,7 +250,7 @@ _blk_read_multiple (nbdkit_next *next, " (offset %" PRIu64 ")", blknum, (uint64_t) offset); - if (pwrite (fd, block, blksize * runblocks, offset) == -1) { + if (full_pwrite (fd, block, blksize * runblocks, offset) == -1) { *err = errno; nbdkit_error ("pwrite: %m"); return -1; @@ -262,7 +262,7 @@ _blk_read_multiple (nbdkit_next *next, } } else { /* Read cache. */ - if (pread (fd, block, blksize * runblocks, offset) == -1) { + if (full_pread (fd, block, blksize * runblocks, offset) == -1) { *err = errno; nbdkit_error ("pread: %m"); return -1; @@ -339,7 +339,7 @@ blk_cache (nbdkit_next *next, nbdkit_debug ("cache: cache block %" PRIu64 " (offset %" PRIu64 ")", blknum, (uint64_t) offset); - if (pwrite (fd, block, blksize, offset) == -1) { + if (full_pwrite (fd, block, blksize, offset) == -1) { *err = errno; nbdkit_error ("pwrite: %m"); return -1; @@ -380,7 +380,7 @@ blk_writethrough (nbdkit_next *next, nbdkit_debug ("cache: writethrough block %" PRIu64 " (offset %" PRIu64 ")", blknum, (uint64_t) offset); - if (pwrite (fd, block, blksize, offset) == -1) { + if (full_pwrite (fd, block, blksize, offset) == -1) { *err = errno; nbdkit_error ("pwrite: %m"); return -1; @@ -414,7 +414,7 @@ blk_write (nbdkit_next *next, nbdkit_debug ("cache: writeback block %" PRIu64 " (offset %" PRIu64 ")", blknum, (uint64_t) offset); - if (pwrite (fd, block, blksize, offset) == -1) { + if (full_pwrite (fd, block, blksize, offset) == -1) { *err = errno; nbdkit_error ("pwrite: %m"); return -1; diff --git a/filters/cow/blk.c b/filters/cow/blk.c index 9e6c8879..cebd9454 100644 --- a/filters/cow/blk.c +++ b/filters/cow/blk.c @@ -282,7 +282,7 @@ blk_read_multiple (nbdkit_next *next, memset (block + n, 0, tail); } else if (state == BLOCK_ALLOCATED) { /* Read overlay. */ - if (pread (fd, block, BLKSIZE * runblocks, offset) == -1) { + if (full_pread (fd, block, BLKSIZE * runblocks, offset) == -1) { *err = errno; nbdkit_error ("pread: %m"); return -1; @@ -357,7 +357,7 @@ blk_cache (nbdkit_next *next, memset (block + n, 0, tail); if (mode == BLK_CACHE_COW) { - if (pwrite (fd, block, BLKSIZE, offset) == -1) { + if (full_pwrite (fd, block, BLKSIZE, offset) == -1) { *err = errno; nbdkit_error ("pwrite: %m"); return -1; @@ -376,7 +376,7 @@ blk_write (uint64_t blknum, const uint8_t *block, int *err) nbdkit_debug ("cow: blk_write block %" PRIu64 " (offset %" PRIu64 ")", blknum, (uint64_t) offset); - if (pwrite (fd, block, BLKSIZE, offset) == -1) { + if (full_pwrite (fd, block, BLKSIZE, offset) == -1) { *err = errno; nbdkit_error ("pwrite: %m"); return -1; -- 2.32.0
Richard W.M. Jones
2021-Jul-26 17:28 UTC
[Libguestfs] [PATCH nbdkit 5/7] cache: Implement cache-on-read=/PATH
For virt-v2v we will need to be able to turn cache-on-read on while performing inspection and modification of the guest, and later off when doing the bulk copy. To do that allow the cache-on-read parameter to refer to a path where the existence of the path toggles the feature. (We could restart nbdkit between these phases, but this change avoids doing that.) --- filters/cache/nbdkit-cache-filter.pod | 11 ++++++++- filters/cache/cache.h | 10 ++++++-- filters/cache/blk.c | 2 +- filters/cache/cache.c | 33 ++++++++++++++++++++------- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/filters/cache/nbdkit-cache-filter.pod b/filters/cache/nbdkit-cache-filter.pod index 34fd0b29..2ac307e0 100644 --- a/filters/cache/nbdkit-cache-filter.pod +++ b/filters/cache/nbdkit-cache-filter.pod @@ -8,7 +8,7 @@ nbdkit-cache-filter - nbdkit caching filter [cache-max-size=SIZE] [cache-high-threshold=N] [cache-low-threshold=N] - [cache-on-read=true|false] + [cache-on-read=true|false|/PATH] [plugin-args...] =head1 DESCRIPTION @@ -87,6 +87,15 @@ the plugin. Do not cache read requests (this is the default). +=item B<cache-on-read=/PATH> + +(nbdkit E<ge> 1.28) + +When F</PATH> (which must be an absolute path) exists, this behaves +like C<cache-on-read=true>, and when it does not exist like +C<cache-on-read=false>. This allows you to control the cache-on-read +behaviour while nbdkit is running. + =back =head1 CACHE MAXIMUM SIZE diff --git a/filters/cache/cache.h b/filters/cache/cache.h index 2b72221f..a559adef 100644 --- a/filters/cache/cache.h +++ b/filters/cache/cache.h @@ -49,7 +49,13 @@ extern unsigned blksize; extern int64_t max_size; extern unsigned hi_thresh, lo_thresh; -/* Cache read requests. */ -extern bool cache_on_read; +/* Cache on read mode. */ +extern enum cor_mode { + COR_OFF, + COR_ON, + COR_PATH, +} cor_mode; +extern const char *cor_path; +extern bool cache_on_read (void); #endif /* NBDKIT_CACHE_H */ diff --git a/filters/cache/blk.c b/filters/cache/blk.c index 42bd3779..19f79605 100644 --- a/filters/cache/blk.c +++ b/filters/cache/blk.c @@ -244,7 +244,7 @@ _blk_read_multiple (nbdkit_next *next, memset (block + n, 0, tail); /* If cache-on-read, copy the blocks to the cache. */ - if (cache_on_read) { + if (cache_on_read ()) { if (cache_debug_verbose) nbdkit_debug ("cache: cache-on-read block %" PRIu64 " (offset %" PRIu64 ")", diff --git a/filters/cache/cache.c b/filters/cache/cache.c index 9c081948..8af52106 100644 --- a/filters/cache/cache.c +++ b/filters/cache/cache.c @@ -74,7 +74,8 @@ unsigned blksize; enum cache_mode cache_mode = CACHE_MODE_WRITEBACK; int64_t max_size = -1; unsigned hi_thresh = 95, lo_thresh = 80; -bool cache_on_read = false; +enum cor_mode cor_mode = COR_OFF; +const char *cor_path; static int cache_flush (nbdkit_next *next, void *handle, uint32_t flags, int *err); @@ -161,12 +162,16 @@ cache_config (nbdkit_next_config *next, nbdkit_backend *nxdata, } #endif /* !HAVE_CACHE_RECLAIM */ else if (strcmp (key, "cache-on-read") == 0) { - int r; - - r = nbdkit_parse_bool (value); - if (r == -1) - return -1; - cache_on_read = r; + if (value[0] == '/') { + cor_path = value; + cor_mode = COR_PATH; + } + else { + int r = nbdkit_parse_bool (value); + if (r == -1) + return -1; + cor_mode = r ? COR_ON : COR_OFF; + } return 0; } else { @@ -177,7 +182,7 @@ cache_config (nbdkit_next_config *next, nbdkit_backend *nxdata, #define cache_config_help_common \ "cache=MODE Set cache MODE, one of writeback (default),\n" \ " writethrough, or unsafe.\n" \ - "cache-on-read=BOOL Set to true to cache on reads (default false).\n" + "cache-on-read=BOOL|/PATH Set to true to cache on reads (default false).\n" #ifndef HAVE_CACHE_RECLAIM #define cache_config_help cache_config_help_common #else @@ -187,6 +192,18 @@ cache_config (nbdkit_next_config *next, nbdkit_backend *nxdata, "cache-low-threshold=PCT Percentage of max size where reclaim ends.\n" #endif +/* Decide if cache-on-read is currently on or off. */ +bool +cache_on_read (void) +{ + switch (cor_mode) { + case COR_ON: return true; + case COR_OFF: return false; + case COR_PATH: return access (cor_path, F_OK) == 0; + default: abort (); + } +} + static int cache_config_complete (nbdkit_next_config_complete *next, nbdkit_backend *nxdata) -- 2.32.0
Richard W.M. Jones
2021-Jul-26 17:28 UTC
[Libguestfs] [PATCH nbdkit 6/7] cache: Add cache-min-block-size parameter
This allows you to choose a larger block size. I found experimentally that this improves performance because of locality in access patterns. The idea came from qcow2 which implicitly does the same thing because of the relatively large cluster size (32K). nbdkit + cache-filter with 4K block size + copy-on-read + curl (to a very slow remote site): => virt-inspector took 22 mins same with 64K block size: => virt-inspector took 19 mins However compared to a qcow2 file using copy-on-read, backed with nbdkit + curl (ie. implicit 32K block size) we are still a lot slower, possibly because having the cache inside virt-inspector greatly reduces round trips: => virt-inspector took 13 mins --- filters/cache/nbdkit-cache-filter.pod | 9 ++++ tests/Makefile.am | 2 + filters/cache/cache.h | 3 ++ filters/cache/blk.c | 2 +- filters/cache/cache.c | 36 ++++++++++---- tests/test-cache-block-size.sh | 70 +++++++++++++++++++++++++++ 6 files changed, 112 insertions(+), 10 deletions(-) diff --git a/filters/cache/nbdkit-cache-filter.pod b/filters/cache/nbdkit-cache-filter.pod index 2ac307e0..9511e91b 100644 --- a/filters/cache/nbdkit-cache-filter.pod +++ b/filters/cache/nbdkit-cache-filter.pod @@ -5,6 +5,7 @@ nbdkit-cache-filter - nbdkit caching filter =head1 SYNOPSIS nbdkit --filter=cache plugin [cache=writeback|writethrough|unsafe] + [cache-min-block-size=SIZE] [cache-max-size=SIZE] [cache-high-threshold=N] [cache-low-threshold=N] @@ -59,6 +60,14 @@ This is dangerous and can cause data loss, but this may be acceptable if you only use it for testing or with data that you don't care about or can cheaply reconstruct. +=item B<cache-min-block-size=>SIZE + +Set the minimum block size used by the cache. This must be a power of +2 and E<ge> 4096. + +The default is 4096, or the block size of the filesystem which +contains the temporary file storing the cache (whichever is larger). + =item B<cache-max-size=>SIZE =item B<cache-high-threshold=>N diff --git a/tests/Makefile.am b/tests/Makefile.am index 8e0304d4..7146a2d1 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1373,12 +1373,14 @@ EXTRA_DIST += test-blocksize.sh test-blocksize-extents.sh # cache filter test. TESTS += \ test-cache.sh \ + test-cache-block-size.sh \ test-cache-on-read.sh \ test-cache-max-size.sh \ test-cache-unaligned.sh \ $(NULL) EXTRA_DIST += \ test-cache.sh \ + test-cache-block-size.sh \ test-cache-on-read.sh \ test-cache-max-size.sh \ test-cache-unaligned.sh \ diff --git a/filters/cache/cache.h b/filters/cache/cache.h index a559adef..5c32c37c 100644 --- a/filters/cache/cache.h +++ b/filters/cache/cache.h @@ -45,6 +45,9 @@ extern enum cache_mode { /* Size of a block in the cache. */ extern unsigned blksize; +/* Minimum block size (cache-min-block-size parameter). */ +extern unsigned min_block_size; + /* Maximum size of the cache and high/low thresholds. */ extern int64_t max_size; extern unsigned hi_thresh, lo_thresh; diff --git a/filters/cache/blk.c b/filters/cache/blk.c index 19f79605..6276985f 100644 --- a/filters/cache/blk.c +++ b/filters/cache/blk.c @@ -149,7 +149,7 @@ blk_init (void) nbdkit_error ("fstatvfs: %s: %m", tmpdir); return -1; } - blksize = MAX (4096, statvfs.f_bsize); + blksize = MAX (min_block_size, statvfs.f_bsize); nbdkit_debug ("cache: block size: %u", blksize); bitmap_init (&bm, blksize, 2 /* bits per block */); diff --git a/filters/cache/cache.c b/filters/cache/cache.c index 8af52106..48a20c3b 100644 --- a/filters/cache/cache.c +++ b/filters/cache/cache.c @@ -40,6 +40,7 @@ #include <inttypes.h> #include <unistd.h> #include <fcntl.h> +#include <limits.h> #include <errno.h> #include <assert.h> #include <sys/types.h> @@ -62,6 +63,7 @@ #include "blk.h" #include "reclaim.h" #include "isaligned.h" +#include "ispowerof2.h" #include "minmax.h" #include "rounding.h" @@ -70,7 +72,8 @@ */ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; -unsigned blksize; +unsigned blksize; /* actual block size (picked by blk.c) */ +unsigned min_block_size = 4096; enum cache_mode cache_mode = CACHE_MODE_WRITEBACK; int64_t max_size = -1; unsigned hi_thresh = 95, lo_thresh = 80; @@ -80,13 +83,6 @@ const char *cor_path; static int cache_flush (nbdkit_next *next, void *handle, uint32_t flags, int *err); -static void -cache_load (void) -{ - if (blk_init () == -1) - exit (EXIT_FAILURE); -} - static void cache_unload (void) { @@ -116,6 +112,19 @@ cache_config (nbdkit_next_config *next, nbdkit_backend *nxdata, return -1; } } + else if (strcmp (key, "cache-min-block-size") == 0) { + int64_t r; + + r = nbdkit_parse_size (value); + if (r == -1) + return -1; + if (r < 4096 || !is_power_of_2 (r) || r > UINT_MAX) { + nbdkit_error ("cache-min-block-size is not a power of 2, or is too small or too large"); + return -1; + } + min_block_size = r; + return 0; + } #ifdef HAVE_CACHE_RECLAIM else if (strcmp (key, "cache-max-size") == 0) { int64_t r; @@ -220,6 +229,15 @@ cache_config_complete (nbdkit_next_config_complete *next, return next (nxdata); } +static int +cache_get_ready (int thread_model) +{ + if (blk_init () == -1) + return -1; + + return 0; +} + /* Get the file size, set the cache size. */ static int64_t cache_get_size (nbdkit_next *next, @@ -691,11 +709,11 @@ cache_cache (nbdkit_next *next, static struct nbdkit_filter filter = { .name = "cache", .longname = "nbdkit caching filter", - .load = cache_load, .unload = cache_unload, .config = cache_config, .config_complete = cache_config_complete, .config_help = cache_config_help, + .get_ready = cache_get_ready, .prepare = cache_prepare, .get_size = cache_get_size, .can_cache = cache_can_cache, diff --git a/tests/test-cache-block-size.sh b/tests/test-cache-block-size.sh new file mode 100755 index 00000000..a2a27407 --- /dev/null +++ b/tests/test-cache-block-size.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# nbdkit +# Copyright (C) 2018-2021 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Red Hat nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +source ./functions.sh +set -e +set -x + +requires_filter cache +requires_nbdsh_uri + +sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX) +files="cache-block-size.img $sock cache-block-size.pid" +rm -f $files +cleanup_fn rm -f $files + +# Create an empty base image. +truncate -s 128K cache-block-size.img + +# Run nbdkit with the caching filter. +start_nbdkit -P cache-block-size.pid -U $sock --filter=cache \ + file cache-block-size.img cache-min-block-size=64K + +nbdsh --connect "nbd+unix://?socket=$sock" \ + -c ' +# Write some pattern data to the overlay and check it reads back OK. +buf = b"abcd" * 16384 +h.pwrite(buf, 32768) +zero = h.pread(32768, 0) +assert zero == bytearray(32768) +buf2 = h.pread(65536, 32768) +assert buf == buf2 + +# Flushing should write through to the underlying file. +h.flush() + +with open("cache-block-size.img", "rb") as file: + zero = file.read(32768) + assert zero == bytearray(32768) + buf2 = file.read(65536) + assert buf == buf2 +' -- 2.32.0
Richard W.M. Jones
2021-Jul-26 17:29 UTC
[Libguestfs] [PATCH nbdkit 7/7] cache, cow: Use a 64K block size by default
Based on the results presented in the previous commit, use a 64K block size by default in both the cache and cow filters. For the cache filter you could go back to a 4K block size if you wanted by using the cache-min-block-size=4K parameter. For cow it is compiled in so cannot be adjusted. --- filters/cache/nbdkit-cache-filter.pod | 4 ++-- filters/cow/blk.h | 2 +- filters/cache/cache.c | 2 +- tests/test-cache-block-size.sh | 2 +- tests/test-cow-extents1.sh | 33 +++++++++++++++------------ 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/filters/cache/nbdkit-cache-filter.pod b/filters/cache/nbdkit-cache-filter.pod index 9511e91b..99707373 100644 --- a/filters/cache/nbdkit-cache-filter.pod +++ b/filters/cache/nbdkit-cache-filter.pod @@ -65,8 +65,8 @@ or can cheaply reconstruct. Set the minimum block size used by the cache. This must be a power of 2 and E<ge> 4096. -The default is 4096, or the block size of the filesystem which -contains the temporary file storing the cache (whichever is larger). +The default is 64K, or the block size of the filesystem which contains +the temporary file storing the cache (whichever is larger). =item B<cache-max-size=>SIZE diff --git a/filters/cow/blk.h b/filters/cow/blk.h index b066c602..1bc85283 100644 --- a/filters/cow/blk.h +++ b/filters/cow/blk.h @@ -36,7 +36,7 @@ /* Size of a block in the overlay. A 4K block size means that we need * 64 MB of memory to store the bitmap for a 1 TB underlying image. */ -#define BLKSIZE 4096 +#define BLKSIZE 65536 /* Initialize the overlay and bitmap. */ extern int blk_init (void); diff --git a/filters/cache/cache.c b/filters/cache/cache.c index 48a20c3b..f7b01039 100644 --- a/filters/cache/cache.c +++ b/filters/cache/cache.c @@ -73,7 +73,7 @@ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; unsigned blksize; /* actual block size (picked by blk.c) */ -unsigned min_block_size = 4096; +unsigned min_block_size = 65536; enum cache_mode cache_mode = CACHE_MODE_WRITEBACK; int64_t max_size = -1; unsigned hi_thresh = 95, lo_thresh = 80; diff --git a/tests/test-cache-block-size.sh b/tests/test-cache-block-size.sh index a2a27407..d20cc940 100755 --- a/tests/test-cache-block-size.sh +++ b/tests/test-cache-block-size.sh @@ -47,7 +47,7 @@ truncate -s 128K cache-block-size.img # Run nbdkit with the caching filter. start_nbdkit -P cache-block-size.pid -U $sock --filter=cache \ - file cache-block-size.img cache-min-block-size=64K + file cache-block-size.img cache-min-block-size=4K nbdsh --connect "nbd+unix://?socket=$sock" \ -c ' diff --git a/tests/test-cow-extents1.sh b/tests/test-cow-extents1.sh index 8e0e0383..ebfd83f6 100755 --- a/tests/test-cow-extents1.sh +++ b/tests/test-cow-extents1.sh @@ -65,7 +65,7 @@ cleanup_fn rm -f $files # Create a base file which is half allocated, half sparse. dd if=/dev/urandom of=$base count=128 bs=1K -truncate -s 256K $base +truncate -s 4M $base lastmod="$(stat -c "%y" $base)" # Run nbdkit with a COW overlay. @@ -76,30 +76,33 @@ uri="nbd+unix:///?socket=$sock" nbdinfo --map "$uri" > $out cat $out if [ "$(tr -s ' ' < $out | cut -d' ' -f 1-4)" != " 0 131072 0 - 131072 131072 3" ]; then + 131072 4063232 3" ]; then echo "$0: unexpected initial file map" exit 1 fi # Punch some holes. nbdsh -u "$uri" \ - -c 'h.trim(4096, 4096)' \ - -c 'h.trim(4098, 16383)' \ - -c 'h.pwrite(b"1"*4096, 65536)' \ - -c 'h.trim(8192, 131072)' \ - -c 'h.pwrite(b"2"*8192, 196608)' + -c 'bs = 65536' \ + -c 'h.trim(bs, bs)' \ + -c 'h.trim(bs+2, 4*bs-1)' \ + -c 'h.pwrite(b"1"*bs, 16*bs)' \ + -c 'h.trim(2*bs, 32*bs)' \ + -c 'h.pwrite(b"2"*(2*bs), 48*bs)' # The extents map should be fully allocated. nbdinfo --map "$uri" > $out cat $out -if [ "$(tr -s ' ' < $out | cut -d' ' -f 1-4)" != " 0 4096 0 - 4096 4096 3 - 8192 8192 0 - 16384 4096 3 - 20480 110592 0 - 131072 65536 3 - 196608 8192 0 - 204800 57344 3" ]; then +if [ "$(tr -s ' ' < $out | cut -d' ' -f 1-4)" != " 0 65536 0 + 65536 131072 3 + 196608 65536 0 + 262144 65536 3 + 327680 65536 0 + 393216 655360 3 + 1048576 65536 0 + 1114112 2031616 3 + 3145728 131072 0 + 3276800 917504 3" ]; then echo "$0: unexpected trimmed file map" exit 1 fi -- 2.32.0
Eric Blake
2021-Jul-26 20:56 UTC
[Libguestfs] [PATCH nbdkit 0/7] Miscellaneous virt-v2v changes to cache and cow filters
On Mon, Jul 26, 2021 at 06:28:53PM +0100, Richard W.M. Jones wrote:> I've spent some time today trying to optimize modular virt-v2v a bit, > and these are some suggested changes to nbdkit which should help.Series looks good to me. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org