375 lines
13 KiB
Diff
375 lines
13 KiB
Diff
From fc77a2a50faa1d1e6b10310f84384d2ad18dec47 Mon Sep 17 00:00:00 2001
|
|
From: Zdenek Kabelac <zkabelac@redhat.com>
|
|
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 <math.h>
|
|
+
|
|
+/*
|
|
+ * 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
|
|
|