diff --git a/include/spdk/reduce.h b/include/spdk/reduce.h index d71aafe26..a28d204ae 100644 --- a/include/spdk/reduce.h +++ b/include/spdk/reduce.h @@ -38,4 +38,50 @@ #ifndef SPDK_REDUCE_H_ #define SPDK_REDUCE_H_ +#include "spdk/uuid.h" + +/** + * Describes the parameters of an spdk_reduce_vol. + */ +struct spdk_reduce_vol_params { + /** + * Size in bytes of the IO unit for the backing device. This + * is the unit in which space is allocated from the backing + * device, and the unit in which data is read from of written + * to the backing device. Must be greater than 0. + */ + uint32_t backing_io_unit_size; + + /** + * Size in bytes of a chunk on the compressed volume. This + * is the unit in which data is compressed. Must be an even + * multiple of backing_io_unit_size. Must be greater than 0. + */ + uint32_t chunk_size; + + /** + * Total size in bytes of the compressed volume. Must be + * an even multiple of chunk_size. Must be greater than 0. + */ + uint64_t vol_size; +}; + +/** + * Get the required size for the pm file for a compressed volume. + * + * \param params Parameters for the compressed volume + * \return Size of the required pm file (in bytes) needed to create the + * compressed volume. Returns -EINVAL if params is invalid. + */ +int64_t spdk_reduce_get_pm_file_size(struct spdk_reduce_vol_params *params); + +/** + * Get the required size for the backing device for a compressed volume. + * + * \param params Parameters for the compressed volume + * \return Size of the required backing device (in bytes) needed to create + * the compressed volume. Returns -EINVAL if params is invalid. + */ +int64_t spdk_reduce_get_backing_device_size(struct spdk_reduce_vol_params *params); + #endif /* SPDK_REDUCE_H_ */ diff --git a/lib/reduce/reduce.c b/lib/reduce/reduce.c index 1cb671b01..e78ded5ce 100644 --- a/lib/reduce/reduce.c +++ b/lib/reduce/reduce.c @@ -36,4 +36,122 @@ #include "spdk/reduce.h" #include "spdk_internal/log.h" +/* Always round up the size of the PM region to the nearest cacheline. */ +#define REDUCE_PM_SIZE_ALIGNMENT 64 + +/* Structure written to offset 0 of both the pm file and the backing device. */ +struct spdk_reduce_vol_superblock { + struct spdk_reduce_vol_params params; + uint8_t reserved[4080]; +}; +SPDK_STATIC_ASSERT(sizeof(struct spdk_reduce_vol_superblock) == 4096, "size incorrect"); + +struct spdk_reduce_vol { +}; + +/* + * Allocate extra metadata chunks and corresponding backing io units to account for + * outstanding IO in worst case scenario where logical map is completely allocated + * and no data can be compressed. We need extra chunks in this case to handle + * in-flight writes since reduce never writes data in place. + */ +#define REDUCE_NUM_EXTRA_CHUNKS 128 + +static inline uint64_t +divide_round_up(uint64_t num, uint64_t divisor) +{ + return (num + divisor - 1) / divisor; +} + +static uint64_t +_get_pm_logical_map_size(uint64_t vol_size, uint64_t chunk_size) +{ + uint64_t chunks_in_logical_map, logical_map_size; + + chunks_in_logical_map = vol_size / chunk_size; + logical_map_size = chunks_in_logical_map * sizeof(uint64_t); + + /* Round up to next cacheline. */ + return divide_round_up(logical_map_size, 64) * 64; +} + +static uint64_t +_get_total_chunks(uint64_t vol_size, uint64_t chunk_size) +{ + uint64_t num_chunks; + + num_chunks = vol_size / chunk_size; + num_chunks += REDUCE_NUM_EXTRA_CHUNKS; + + return num_chunks; +} + +static uint64_t +_get_pm_total_chunks_size(uint64_t vol_size, uint64_t chunk_size, uint64_t backing_io_unit_size) +{ + uint64_t io_units_per_chunk, num_chunks, total_chunks_size; + + num_chunks = _get_total_chunks(vol_size, chunk_size); + io_units_per_chunk = chunk_size / backing_io_unit_size; + total_chunks_size = num_chunks * io_units_per_chunk * sizeof(uint64_t); + + return divide_round_up(total_chunks_size, REDUCE_PM_SIZE_ALIGNMENT) * REDUCE_PM_SIZE_ALIGNMENT; +} + +static int +_validate_vol_params(struct spdk_reduce_vol_params *params) +{ + if (params->vol_size == 0 || params->chunk_size == 0 || params->backing_io_unit_size == 0) { + return -EINVAL; + } + + /* Chunk size must be an even multiple of the backing io unit size. */ + if ((params->chunk_size % params->backing_io_unit_size) != 0) { + return -EINVAL; + } + + /* Volume size must be an even multiple of the chunk size. */ + if ((params->vol_size % params->chunk_size) != 0) { + return -EINVAL; + } + + return 0; +} + +int64_t +spdk_reduce_get_pm_file_size(struct spdk_reduce_vol_params *params) +{ + uint64_t total_pm_size; + int rc; + + rc = _validate_vol_params(params); + if (rc != 0) { + return rc; + } + + total_pm_size = sizeof(struct spdk_reduce_vol_superblock); + total_pm_size += _get_pm_logical_map_size(params->vol_size, params->chunk_size); + total_pm_size += _get_pm_total_chunks_size(params->vol_size, params->chunk_size, + params->backing_io_unit_size); + return total_pm_size; +} + +int64_t +spdk_reduce_get_backing_device_size(struct spdk_reduce_vol_params *params) +{ + uint64_t total_backing_size, num_chunks; + int rc; + + rc = _validate_vol_params(params); + if (rc != 0) { + return rc; + } + + num_chunks = _get_total_chunks(params->vol_size, params->chunk_size); + total_backing_size = num_chunks * params->chunk_size; + total_backing_size += sizeof(struct spdk_reduce_vol_superblock); + + return total_backing_size; +} + SPDK_LOG_REGISTER_COMPONENT("reduce", SPDK_LOG_REDUCE) diff --git a/test/unit/lib/reduce/reduce.c/reduce_ut.c b/test/unit/lib/reduce/reduce.c/reduce_ut.c index c1072fae1..5fc4ca075 100644 --- a/test/unit/lib/reduce/reduce.c/reduce_ut.c +++ b/test/unit/lib/reduce/reduce.c/reduce_ut.c @@ -39,9 +39,85 @@ #include "common/lib/test_env.c" static void -empty_test(void) +get_pm_file_size(void) { - CU_ASSERT(true); + struct spdk_reduce_vol_params params; + int64_t pm_size, expected_pm_size; + + params.vol_size = 0; + params.chunk_size = 0; + params.backing_io_unit_size = 0; + CU_ASSERT(spdk_reduce_get_pm_file_size(¶ms) == -EINVAL); + + /* + * Select a valid backing_io_unit_size. This should still fail since + * vol_size and chunk_size are still 0. + */ + params.backing_io_unit_size = 4096; + CU_ASSERT(spdk_reduce_get_pm_file_size(¶ms) == -EINVAL); + + /* + * Select a valid chunk_size. This should still fail since val_size + * is still 0. + */ + params.chunk_size = 4096 * 4; + CU_ASSERT(spdk_reduce_get_pm_file_size(¶ms) == -EINVAL); + + /* Select a valid vol_size. This should return a proper pm_size. */ + params.vol_size = 4096 * 4 * 100; + pm_size = spdk_reduce_get_pm_file_size(¶ms); + expected_pm_size = sizeof(struct spdk_reduce_vol_superblock); + /* 100 chunks in logical map * 8 bytes per chunk */ + expected_pm_size += 100 * sizeof(uint64_t); + /* 100 chunks * 4 backing io units per chunk * 8 bytes per backing io unit */ + expected_pm_size += 100 * 4 * sizeof(uint64_t); + /* reduce allocates some extra chunks too for in-flight writes when logical map + * is full. REDUCE_EXTRA_CHUNKS is a private #ifdef in reduce.c. + */ + expected_pm_size += REDUCE_NUM_EXTRA_CHUNKS * 4 * sizeof(uint64_t); + /* reduce will add some padding so numbers may not match exactly. Make sure + * they are close though. + */ + CU_ASSERT((pm_size - expected_pm_size) < REDUCE_PM_SIZE_ALIGNMENT); +} + +static void +get_backing_device_size(void) +{ + struct spdk_reduce_vol_params params; + int64_t backing_size, expected_backing_size; + + params.vol_size = 0; + params.chunk_size = 0; + params.backing_io_unit_size = 0; + CU_ASSERT(spdk_reduce_get_backing_device_size(¶ms) == -EINVAL); + + /* + * Select a valid backing_io_unit_size. This should still fail since + * vol_size and chunk_size are still 0. + */ + params.backing_io_unit_size = 4096; + CU_ASSERT(spdk_reduce_get_backing_device_size(¶ms) == -EINVAL); + + /* + * Select a valid chunk_size. This should still fail since val_size + * is still 0. + */ + params.chunk_size = 4096 * 4; + CU_ASSERT(spdk_reduce_get_backing_device_size(¶ms) == -EINVAL); + + /* Select a valid vol_size. This should return a proper backing device size. */ + params.vol_size = 4096 * 4 * 100; + backing_size = spdk_reduce_get_backing_device_size(¶ms); + expected_backing_size = params.vol_size; + /* reduce allocates some extra chunks too for in-flight writes when logical map + * is full. REDUCE_EXTRA_CHUNKS is a private #ifdef in reduce.c. Backing device + * must have space allocated for these extra chunks. + */ + expected_backing_size += REDUCE_NUM_EXTRA_CHUNKS * params.chunk_size; + /* Account for superblock as well. */ + expected_backing_size += sizeof(struct spdk_reduce_vol_superblock); + CU_ASSERT(backing_size == expected_backing_size); } int @@ -61,7 +137,8 @@ main(int argc, char **argv) } if ( - CU_add_test(suite, "empty", empty_test) == NULL + CU_add_test(suite, "get_pm_file_size", get_pm_file_size) == NULL || + CU_add_test(suite, "get_backing_device_size", get_backing_device_size) == NULL ) { CU_cleanup_registry(); return CU_get_error();