From fc77a2a50faa1d1e6b10310f84384d2ad18dec47 Mon Sep 17 00:00:00 2001 From: Zdenek Kabelac Date: Sun, 26 Apr 2026 11:35:09 +0200 Subject: [PATCH 10/16] vdo: add pre-format pool geometry computation Add vdo_format.c with vdo_pool_info() that replicates the vdoformat pool layout calculation: given a pool's physical size and VDO parameters, compute UDS index size, slab count, per-slab data blocks, and usable data blocks (accounting for block map forest overhead). Return results in struct vdo_pool_info so callers can both set the correct virtual size before kernel formatting and report pool geometry (slab count, addressable capacity, growth limits). (cherry picked from commit d5298389476a869a7e650f0188d6830202986ac6) --- lib/Makefile.in | 1 + lib/metadata/metadata-exported.h | 10 + lib/metadata/vdo_format.c | 310 +++++++++++++++++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 lib/metadata/vdo_format.c diff --git a/lib/Makefile.in b/lib/Makefile.in index bffe5dddc..667534967 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -94,6 +94,7 @@ SOURCES =\ metadata/segtype.c \ metadata/snapshot_manip.c \ metadata/thin_manip.c \ + metadata/vdo_format.c \ metadata/vdo_manip.c \ metadata/vg.c \ mirror/mirrored.c \ diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h index 4318477f4..1f78c7aac 100644 --- a/lib/metadata/metadata-exported.h +++ b/lib/metadata/metadata-exported.h @@ -1388,6 +1388,16 @@ const char *get_vdo_index_state_name(enum dm_vdo_index_state state); const char *get_vdo_operating_mode_name(enum dm_vdo_operating_mode mode); const char *get_vdo_write_policy_name(enum dm_vdo_write_policy policy); uint64_t get_vdo_pool_virtual_size(const struct lv_segment *vdo_pool_seg); +struct vdo_pool_info { + uint64_t data_blocks; + uint64_t uds_blocks; + unsigned slab_count; + unsigned data_per_slab; +}; + +int vdo_pool_info(uint64_t pool_size_sectors, + const struct dm_vdo_target_params *vtp, + struct vdo_pool_info *info); int update_vdo_pool_virtual_size(struct lv_segment *vdo_pool_seg); uint32_t get_vdo_pool_max_extents(const struct dm_vdo_target_params *vtp, uint32_t extent_size); diff --git a/lib/metadata/vdo_format.c b/lib/metadata/vdo_format.c new file mode 100644 index 000000000..48b0e6a2c --- /dev/null +++ b/lib/metadata/vdo_format.c @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2026 Red Hat, Inc. All rights reserved. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "lib/misc/lib.h" +#include "lib/metadata/metadata.h" + +#include + +/* + * Compute VDO data blocks available for storing compressed/deduplicated content. + * + * Replicates the vdoformat calculation: given a pool's physical size and + * VDO parameters, compute the usable data blocks at 1:1 mapping + * (accounting for UDS index, slab overhead, and block map forest). + */ + +/* UDS index geometry constants */ +#define UDS_BLOCK_SIZE 4096U +#define UDS_BYTES_PER_RECORD 32U +#define UDS_BYTES_PER_PAGE 32768U +#define UDS_RECORDS_PER_PAGE (UDS_BYTES_PER_PAGE / UDS_BYTES_PER_RECORD) /* 1024 */ +#define UDS_DEFAULT_CHAPTERS 1024U +#define UDS_SMALL_RPC 64U +#define UDS_DEFAULT_RPC 256U +#define UDS_DELTA_LIST_SIZE 256U +#define UDS_MAX_ZONES 16U +#define UDS_MAX_SAVES 2U +#define UDS_HEADER_SIZE 19U /* immutable delta index header */ +#define UDS_CHAPTER_MEAN_DELTA_BITS 16U /* chapter mean_delta = 1<<16 */ +#define UDS_VOLUME_INDEX_MEAN_DELTA 4096U +#define UDS_SPARSE_SAMPLE_RATE 32U + +/* UDS on-disk structure sizes */ +#define UDS_SIZEOF_DI_HEADER 40U +#define UDS_SIZEOF_DL_SAVE_INFO 8U +#define UDS_SIZEOF_DP_HEADER 20U +#define UDS_SIZEOF_SUB_INDEX_DATA 40U +#define UDS_SIZEOF_VI_DATA 12U /* magic[8] + u32 sparse_sample_rate */ +#define UDS_PAGE_MAP_MAGIC_LEN 8U +#define UDS_OPEN_CHAPTER_MAGIC_LEN (sizeof("ALBOC") - 1) +#define UDS_OPEN_CHAPTER_VER_LEN (sizeof("02.00") - 1) + +/* VDO layout constants */ +#define VDO_BLOCK_MAP_TREE_HEIGHT 5U +#define VDO_BLOCK_MAP_ROOT_COUNT 60U +#define VDO_BLOCK_MAP_ENTRIES_PER_PAGE 812U +#define VDO_SLAB_JOURNAL_SIZE 224U /* blocks per slab for journal */ +#define VDO_SLAB_SUMMARY_BLOCKS 64U +#define VDO_RECOVERY_JOURNAL_SIZE 32768U +#define VDO_COUNTS_PER_BLOCK 4032U /* reference counts per block */ + +static unsigned _bits_per(unsigned n) +{ + if (!n) + return 0; + + return 32 - clz(n); +} + +static unsigned _delta_min_bits(unsigned mean_delta) +{ + unsigned incr_keys = (unsigned)round(log(2.0) * mean_delta); + + return _bits_per(incr_keys + 1); +} + +/* Total bits for one chapter's delta index */ +static uint64_t _delta_index_size(uint64_t entry_count, unsigned mean_delta, + unsigned payload_bits) +{ + unsigned min_bits = _delta_min_bits(mean_delta); + + return entry_count * (payload_bits + min_bits + 1) + entry_count / 2; +} + +/* Number of delta index pages needed for one chapter */ +static unsigned _delta_index_pages(uint64_t entry_count, unsigned list_count, + unsigned mean_delta, unsigned payload_bits) +{ + uint64_t total_bits = _delta_index_size(entry_count, mean_delta, payload_bits); + unsigned bits_per_list = (unsigned)(total_bits / list_count); + unsigned bits_per_page; + + total_bits += (uint64_t)list_count * UDS_HEADER_SIZE; + bits_per_page = (UDS_BYTES_PER_PAGE - UDS_SIZEOF_DP_HEADER) * 8 - + UDS_HEADER_SIZE - bits_per_list; + + return (unsigned)dm_div_up(total_bits, bits_per_page); +} + +/* Save bytes for one volume sub-index */ +static uint64_t _sub_index_save_bytes(unsigned records_per_chapter, + unsigned chapters, unsigned vi_mean) +{ + uint64_t delta_list_records = (uint64_t)records_per_chapter * chapters; + unsigned list_count = (unsigned)(delta_list_records / UDS_DELTA_LIST_SIZE); + unsigned addr_bits, chapter_bits, invalid_chapters; + unsigned chapters_in_vi; + uint64_t entries_in_vi, addr_span; + unsigned mean_delta; + uint64_t chapter_size_bits, index_size_bytes, memory_size; + uint64_t zone_memory, di_save; + + if (list_count < UDS_MAX_ZONES * UDS_MAX_ZONES) + list_count = UDS_MAX_ZONES * UDS_MAX_ZONES; + + addr_bits = _bits_per(vi_mean * UDS_DELTA_LIST_SIZE - 1); + chapter_bits = _bits_per(chapters - 1); + invalid_chapters = chapters / 256; + if (invalid_chapters < 2) + invalid_chapters = 2; + chapters_in_vi = chapters + invalid_chapters; + entries_in_vi = (uint64_t)records_per_chapter * chapters_in_vi; + addr_span = (uint64_t)list_count << addr_bits; + mean_delta = (unsigned)(addr_span / entries_in_vi); + + chapter_size_bits = _delta_index_size(records_per_chapter, mean_delta, + chapter_bits); + index_size_bytes = (chapter_size_bits * chapters_in_vi) / 8; + /* add 6% headroom to reduce delta list rebalancing when index is full */ + memory_size = index_size_bytes * 106 / 100; + + /* zone_mem(1, memory_size): round up to 64K */ + zone_memory = dm_div_up(memory_size, 65536U) * 65536U; + + /* delta_index save = header + list_count*(save_info+1) + zone_memory */ + di_save = UDS_SIZEOF_DI_HEADER + zone_memory + + (uint64_t)list_count * (UDS_SIZEOF_DL_SAVE_INFO + 1); + + /* sub_index save = sub_index_data + list_count*8 + di_save */ + return UDS_SIZEOF_SUB_INDEX_DATA + (uint64_t)list_count * 8 + di_save; +} + +/* + * Compute total UDS index size in 4K blocks. + * + * index_memory_mb: UDS memory config (256, 512, 768, or N*1024) + * use_sparse: nonzero for sparse index + */ +static uint64_t _uds_index_blocks(unsigned index_memory_mb, int use_sparse) +{ + unsigned base_chapters, rpc, chapters, sparse_chapters = 0; + unsigned records_per_chapter, chapter_payload_bits; + unsigned chapter_dl_bits, delta_lists_per_chapter; + unsigned ippc, pages_per_chapter; + uint64_t pages_per_volume, bytes_per_volume, volume_blocks; + uint64_t vi_save_bytes, vi_blocks; + unsigned page_map_blocks, open_chapter_blocks; + unsigned save_blocks; + + /* Map memory config to chapters and records-per-page */ + if (index_memory_mb <= 256) { + base_chapters = UDS_DEFAULT_CHAPTERS; + rpc = UDS_SMALL_RPC; + } else if (index_memory_mb <= 512) { + base_chapters = UDS_DEFAULT_CHAPTERS; + rpc = 2 * UDS_SMALL_RPC; + } else if (index_memory_mb <= 768) { + base_chapters = UDS_DEFAULT_CHAPTERS; + rpc = 3 * UDS_SMALL_RPC; + } else { + base_chapters = (index_memory_mb / 1024) * UDS_DEFAULT_CHAPTERS; + rpc = UDS_DEFAULT_RPC; + } + + /* sparse index: 10x total chapters, 95% sparse (19/2 sparse per base chapter) */ + if (use_sparse) { + sparse_chapters = (19 * base_chapters) / 2; + chapters = base_chapters * 10; + } else { + chapters = base_chapters; + } + + /* Geometry */ + records_per_chapter = UDS_RECORDS_PER_PAGE * rpc; + chapter_payload_bits = _bits_per(rpc - 1); + chapter_dl_bits = _bits_per((records_per_chapter - 1) | 077) - 6; + delta_lists_per_chapter = 1U << chapter_dl_bits; + ippc = _delta_index_pages(records_per_chapter, delta_lists_per_chapter, + 1U << UDS_CHAPTER_MEAN_DELTA_BITS, + chapter_payload_bits); + pages_per_chapter = ippc + rpc; + pages_per_volume = (uint64_t)pages_per_chapter * chapters; + bytes_per_volume = UDS_BYTES_PER_PAGE * (pages_per_volume + 1); + volume_blocks = bytes_per_volume / UDS_BLOCK_SIZE; + + /* Volume index save blocks */ + if (!use_sparse) { + vi_save_bytes = _sub_index_save_bytes(records_per_chapter, + chapters, + UDS_VOLUME_INDEX_MEAN_DELTA); + } else { + unsigned hook_rec = records_per_chapter / UDS_SPARSE_SAMPLE_RATE; + unsigned non_hook_rec = records_per_chapter - hook_rec; + unsigned dense_chapters = chapters - sparse_chapters; + uint64_t hook_bytes, non_hook_bytes; + + hook_bytes = _sub_index_save_bytes(hook_rec, chapters, + UDS_VOLUME_INDEX_MEAN_DELTA); + non_hook_bytes = _sub_index_save_bytes(non_hook_rec, + dense_chapters, + UDS_VOLUME_INDEX_MEAN_DELTA); + vi_save_bytes = UDS_SIZEOF_VI_DATA + hook_bytes + non_hook_bytes; + } + vi_blocks = dm_div_up(vi_save_bytes + UDS_SIZEOF_DL_SAVE_INFO, + UDS_BLOCK_SIZE) + UDS_MAX_ZONES; + + /* Page map and open chapter save blocks */ + page_map_blocks = + (unsigned)dm_div_up(UDS_PAGE_MAP_MAGIC_LEN + 8 + (uint64_t)chapters * (ippc - 1) * 2, + UDS_BLOCK_SIZE); + open_chapter_blocks = + (unsigned)dm_div_up(UDS_OPEN_CHAPTER_MAGIC_LEN + UDS_OPEN_CHAPTER_VER_LEN + 4 + + (uint64_t)records_per_chapter * UDS_BYTES_PER_RECORD, + UDS_BLOCK_SIZE); + + save_blocks = 1 + vi_blocks + page_map_blocks + open_chapter_blocks; + + /* total = header(3) + volume + MAX_SAVES * save */ + return 3 + volume_blocks + UDS_MAX_SAVES * save_blocks; +} + +/* + * Compute block map forest overhead: the number of blocks consumed + * by the 5-level block map tree when mapping data_blocks entries. + * Tree roots (level 3) and super roots (level 4) are not allocated + * from slabs, so they are excluded. + */ +static uint64_t _forest_overhead(uint64_t data_blocks) +{ + uint64_t leaf_pages, level_size, non_leaves, leaves; + unsigned levels[VDO_BLOCK_MAP_TREE_HEIGHT]; + unsigned h; + + leaf_pages = dm_div_up(data_blocks, VDO_BLOCK_MAP_ENTRIES_PER_PAGE); + level_size = dm_div_up(leaf_pages, VDO_BLOCK_MAP_ROOT_COUNT); + + for (h = 0; h < VDO_BLOCK_MAP_TREE_HEIGHT; h++) { + level_size = dm_div_up(level_size, VDO_BLOCK_MAP_ENTRIES_PER_PAGE); + levels[h] = (unsigned)level_size; + } + + non_leaves = 0; + for (h = 0; h < VDO_BLOCK_MAP_TREE_HEIGHT; h++) + non_leaves += (uint64_t)levels[h] * VDO_BLOCK_MAP_ROOT_COUNT; + + /* Exclude tree roots and super roots (not from slabs) */ + non_leaves -= (levels[VDO_BLOCK_MAP_TREE_HEIGHT - 2] + + levels[VDO_BLOCK_MAP_TREE_HEIGHT - 1]) * + VDO_BLOCK_MAP_ROOT_COUNT; + + leaves = dm_div_up(data_blocks - non_leaves, + VDO_BLOCK_MAP_ENTRIES_PER_PAGE); + + return non_leaves + leaves; +} + +int vdo_pool_info(uint64_t pool_size_sectors, + const struct dm_vdo_target_params *vtp, + struct vdo_pool_info *info) +{ + uint64_t pool_4k = pool_size_sectors / DM_VDO_BLOCK_SIZE; + unsigned slab_4k = (unsigned)((uint64_t)vtp->slab_size_mb * (1048576 / UDS_BLOCK_SIZE)); + uint64_t offset, depot_blocks; + unsigned ref_blocks; + uint64_t total_data, forest; + + memset(info, 0, sizeof(*info)); + + info->uds_blocks = _uds_index_blocks(vtp->index_memory_size_mb, + vtp->use_sparse_index); + + /* Layout: geometry(1) + UDS + super(1) + roots + slab_summary + recovery_journal */ + offset = info->uds_blocks + 2; + if (pool_4k <= offset + VDO_BLOCK_MAP_ROOT_COUNT + + VDO_SLAB_SUMMARY_BLOCKS + VDO_RECOVERY_JOURNAL_SIZE) + return 0; + + depot_blocks = pool_4k - offset - VDO_BLOCK_MAP_ROOT_COUNT - + VDO_SLAB_SUMMARY_BLOCKS - VDO_RECOVERY_JOURNAL_SIZE; + + info->slab_count = (unsigned)(depot_blocks / slab_4k); + if (!info->slab_count) + return 0; + + /* Slab layout: journal(224) + ref_blocks + data_blocks = slab_4k */ + ref_blocks = (unsigned)dm_div_up(slab_4k - VDO_SLAB_JOURNAL_SIZE, + VDO_COUNTS_PER_BLOCK + 1); + info->data_per_slab = slab_4k - VDO_SLAB_JOURNAL_SIZE - ref_blocks; + total_data = (uint64_t)info->data_per_slab * info->slab_count; + + forest = _forest_overhead(total_data); + if (forest >= total_data) + return 0; + + info->data_blocks = total_data - forest; + + return 1; +} -- 2.54.0