Commit 4390aa13 authored by Matthew Sakai's avatar Matthew Sakai Committed by Mike Snitzer

dm vdo: add deduplication configuration structures

Add structures which record the configuration of various deduplication
index parameters. This also includes facilities for saving and loading the
configuration and validating its integrity.
Co-developed-by: default avatarJ. corwin Coburn <corwin@hurlbutnet.net>
Signed-off-by: default avatarJ. corwin Coburn <corwin@hurlbutnet.net>
Co-developed-by: default avatarMichael Sclafani <dm-devel@lists.linux.dev>
Signed-off-by: default avatarMichael Sclafani <dm-devel@lists.linux.dev>
Co-developed-by: default avatarThomas Jaskiewicz <tom@jaskiewicz.us>
Signed-off-by: default avatarThomas Jaskiewicz <tom@jaskiewicz.us>
Co-developed-by: default avatarJohn Wiele <jwiele@redhat.com>
Signed-off-by: default avatarJohn Wiele <jwiele@redhat.com>
Signed-off-by: default avatarMatthew Sakai <msakai@redhat.com>
Signed-off-by: default avatarMike Snitzer <snitzer@kernel.org>
parent cc46b955
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "config.h"
#include "logger.h"
#include "memory-alloc.h"
#include "numeric.h"
#include "string-utils.h"
#include "uds-threads.h"
static const u8 INDEX_CONFIG_MAGIC[] = "ALBIC";
static const u8 INDEX_CONFIG_VERSION_6_02[] = "06.02";
static const u8 INDEX_CONFIG_VERSION_8_02[] = "08.02";
enum {
DEFAULT_VOLUME_READ_THREADS = 2,
MAX_VOLUME_READ_THREADS = 16,
INDEX_CONFIG_MAGIC_LENGTH = sizeof(INDEX_CONFIG_MAGIC) - 1,
INDEX_CONFIG_VERSION_LENGTH = sizeof(INDEX_CONFIG_VERSION_6_02) - 1,
};
static bool is_version(const u8 *version, u8 *buffer)
{
return memcmp(version, buffer, INDEX_CONFIG_VERSION_LENGTH) == 0;
}
static bool are_matching_configurations(struct configuration *saved_config,
struct geometry *saved_geometry,
struct configuration *user)
{
struct geometry *geometry = user->geometry;
bool result = true;
if (saved_geometry->record_pages_per_chapter != geometry->record_pages_per_chapter) {
uds_log_error("Record pages per chapter (%u) does not match (%u)",
saved_geometry->record_pages_per_chapter,
geometry->record_pages_per_chapter);
result = false;
}
if (saved_geometry->chapters_per_volume != geometry->chapters_per_volume) {
uds_log_error("Chapter count (%u) does not match (%u)",
saved_geometry->chapters_per_volume,
geometry->chapters_per_volume);
result = false;
}
if (saved_geometry->sparse_chapters_per_volume != geometry->sparse_chapters_per_volume) {
uds_log_error("Sparse chapter count (%u) does not match (%u)",
saved_geometry->sparse_chapters_per_volume,
geometry->sparse_chapters_per_volume);
result = false;
}
if (saved_config->cache_chapters != user->cache_chapters) {
uds_log_error("Cache size (%u) does not match (%u)",
saved_config->cache_chapters, user->cache_chapters);
result = false;
}
if (saved_config->volume_index_mean_delta != user->volume_index_mean_delta) {
uds_log_error("Volume index mean delta (%u) does not match (%u)",
saved_config->volume_index_mean_delta,
user->volume_index_mean_delta);
result = false;
}
if (saved_geometry->bytes_per_page != geometry->bytes_per_page) {
uds_log_error("Bytes per page value (%zu) does not match (%zu)",
saved_geometry->bytes_per_page, geometry->bytes_per_page);
result = false;
}
if (saved_config->sparse_sample_rate != user->sparse_sample_rate) {
uds_log_error("Sparse sample rate (%u) does not match (%u)",
saved_config->sparse_sample_rate,
user->sparse_sample_rate);
result = false;
}
if (saved_config->nonce != user->nonce) {
uds_log_error("Nonce (%llu) does not match (%llu)",
(unsigned long long) saved_config->nonce,
(unsigned long long) user->nonce);
result = false;
}
return result;
}
/* Read the configuration and validate it against the provided one. */
int uds_validate_config_contents(struct buffered_reader *reader,
struct configuration *user_config)
{
int result;
struct configuration config;
struct geometry geometry;
u8 version_buffer[INDEX_CONFIG_VERSION_LENGTH];
u32 bytes_per_page;
u8 buffer[sizeof(struct uds_configuration_6_02)];
size_t offset = 0;
result = uds_verify_buffered_data(reader, INDEX_CONFIG_MAGIC,
INDEX_CONFIG_MAGIC_LENGTH);
if (result != UDS_SUCCESS)
return result;
result = uds_read_from_buffered_reader(reader, version_buffer,
INDEX_CONFIG_VERSION_LENGTH);
if (result != UDS_SUCCESS)
return uds_log_error_strerror(result, "cannot read index config version");
if (!is_version(INDEX_CONFIG_VERSION_6_02, version_buffer) &&
!is_version(INDEX_CONFIG_VERSION_8_02, version_buffer)) {
return uds_log_error_strerror(UDS_CORRUPT_DATA,
"unsupported configuration version: '%.*s'",
INDEX_CONFIG_VERSION_LENGTH,
version_buffer);
}
result = uds_read_from_buffered_reader(reader, buffer, sizeof(buffer));
if (result != UDS_SUCCESS)
return uds_log_error_strerror(result, "cannot read config data");
decode_u32_le(buffer, &offset, &geometry.record_pages_per_chapter);
decode_u32_le(buffer, &offset, &geometry.chapters_per_volume);
decode_u32_le(buffer, &offset, &geometry.sparse_chapters_per_volume);
decode_u32_le(buffer, &offset, &config.cache_chapters);
offset += sizeof(u32);
decode_u32_le(buffer, &offset, &config.volume_index_mean_delta);
decode_u32_le(buffer, &offset, &bytes_per_page);
geometry.bytes_per_page = bytes_per_page;
decode_u32_le(buffer, &offset, &config.sparse_sample_rate);
decode_u64_le(buffer, &offset, &config.nonce);
result = ASSERT(offset == sizeof(struct uds_configuration_6_02),
"%zu bytes read but not decoded",
sizeof(struct uds_configuration_6_02) - offset);
if (result != UDS_SUCCESS)
return UDS_CORRUPT_DATA;
if (is_version(INDEX_CONFIG_VERSION_6_02, version_buffer)) {
user_config->geometry->remapped_virtual = 0;
user_config->geometry->remapped_physical = 0;
} else {
u8 remapping[sizeof(u64) + sizeof(u64)];
result = uds_read_from_buffered_reader(reader, remapping,
sizeof(remapping));
if (result != UDS_SUCCESS)
return uds_log_error_strerror(result, "cannot read converted config");
offset = 0;
decode_u64_le(remapping, &offset,
&user_config->geometry->remapped_virtual);
decode_u64_le(remapping, &offset,
&user_config->geometry->remapped_physical);
}
if (!are_matching_configurations(&config, &geometry, user_config)) {
uds_log_warning("Supplied configuration does not match save");
return UDS_NO_INDEX;
}
return UDS_SUCCESS;
}
/*
* Write the configuration to stable storage. If the superblock version is < 4, write the 6.02
* version; otherwise write the 8.02 version, indicating the configuration is for an index that has
* been reduced by one chapter.
*/
int uds_write_config_contents(struct buffered_writer *writer,
struct configuration *config, u32 version)
{
int result;
struct geometry *geometry = config->geometry;
u8 buffer[sizeof(struct uds_configuration_8_02)];
size_t offset = 0;
result = uds_write_to_buffered_writer(writer, INDEX_CONFIG_MAGIC,
INDEX_CONFIG_MAGIC_LENGTH);
if (result != UDS_SUCCESS)
return result;
/*
* If version is < 4, the index has not been reduced by a chapter so it must be written out
* as version 6.02 so that it is still compatible with older versions of UDS.
*/
if (version >= 4) {
result = uds_write_to_buffered_writer(writer, INDEX_CONFIG_VERSION_8_02,
INDEX_CONFIG_VERSION_LENGTH);
if (result != UDS_SUCCESS)
return result;
} else {
result = uds_write_to_buffered_writer(writer, INDEX_CONFIG_VERSION_6_02,
INDEX_CONFIG_VERSION_LENGTH);
if (result != UDS_SUCCESS)
return result;
}
encode_u32_le(buffer, &offset, geometry->record_pages_per_chapter);
encode_u32_le(buffer, &offset, geometry->chapters_per_volume);
encode_u32_le(buffer, &offset, geometry->sparse_chapters_per_volume);
encode_u32_le(buffer, &offset, config->cache_chapters);
encode_u32_le(buffer, &offset, 0);
encode_u32_le(buffer, &offset, config->volume_index_mean_delta);
encode_u32_le(buffer, &offset, geometry->bytes_per_page);
encode_u32_le(buffer, &offset, config->sparse_sample_rate);
encode_u64_le(buffer, &offset, config->nonce);
result = ASSERT(offset == sizeof(struct uds_configuration_6_02),
"%zu bytes encoded, of %zu expected", offset,
sizeof(struct uds_configuration_6_02));
if (result != UDS_SUCCESS)
return result;
if (version >= 4) {
encode_u64_le(buffer, &offset, geometry->remapped_virtual);
encode_u64_le(buffer, &offset, geometry->remapped_physical);
}
return uds_write_to_buffered_writer(writer, buffer, offset);
}
/* Compute configuration parameters that depend on memory size. */
static int compute_memory_sizes(uds_memory_config_size_t mem_gb, bool sparse,
u32 *chapters_per_volume, u32 *record_pages_per_chapter,
u32 *sparse_chapters_per_volume)
{
u32 reduced_chapters = 0;
u32 base_chapters;
if (mem_gb == UDS_MEMORY_CONFIG_256MB) {
base_chapters = DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = SMALL_RECORD_PAGES_PER_CHAPTER;
} else if (mem_gb == UDS_MEMORY_CONFIG_512MB) {
base_chapters = DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = 2 * SMALL_RECORD_PAGES_PER_CHAPTER;
} else if (mem_gb == UDS_MEMORY_CONFIG_768MB) {
base_chapters = DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = 3 * SMALL_RECORD_PAGES_PER_CHAPTER;
} else if ((mem_gb >= 1) && (mem_gb <= UDS_MEMORY_CONFIG_MAX)) {
base_chapters = mem_gb * DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = DEFAULT_RECORD_PAGES_PER_CHAPTER;
} else if (mem_gb == UDS_MEMORY_CONFIG_REDUCED_256MB) {
reduced_chapters = 1;
base_chapters = DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = SMALL_RECORD_PAGES_PER_CHAPTER;
} else if (mem_gb == UDS_MEMORY_CONFIG_REDUCED_512MB) {
reduced_chapters = 1;
base_chapters = DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = 2 * SMALL_RECORD_PAGES_PER_CHAPTER;
} else if (mem_gb == UDS_MEMORY_CONFIG_REDUCED_768MB) {
reduced_chapters = 1;
base_chapters = DEFAULT_CHAPTERS_PER_VOLUME;
*record_pages_per_chapter = 3 * SMALL_RECORD_PAGES_PER_CHAPTER;
} else if ((mem_gb >= 1 + UDS_MEMORY_CONFIG_REDUCED) &&
(mem_gb <= UDS_MEMORY_CONFIG_REDUCED_MAX)) {
reduced_chapters = 1;
base_chapters = ((mem_gb - UDS_MEMORY_CONFIG_REDUCED) *
DEFAULT_CHAPTERS_PER_VOLUME);
*record_pages_per_chapter = DEFAULT_RECORD_PAGES_PER_CHAPTER;
} else {
uds_log_error("received invalid memory size");
return -EINVAL;
}
if (sparse) {
/* Make 95% of chapters sparse, allowing 10x more records. */
*sparse_chapters_per_volume = (19 * base_chapters) / 2;
base_chapters *= 10;
} else {
*sparse_chapters_per_volume = 0;
}
*chapters_per_volume = base_chapters - reduced_chapters;
return UDS_SUCCESS;
}
static unsigned int __must_check normalize_zone_count(unsigned int requested)
{
unsigned int zone_count = requested;
if (zone_count == 0)
zone_count = num_online_cpus() / 2;
if (zone_count < 1)
zone_count = 1;
if (zone_count > MAX_ZONES)
zone_count = MAX_ZONES;
uds_log_info("Using %u indexing zone%s for concurrency.",
zone_count, zone_count == 1 ? "" : "s");
return zone_count;
}
static unsigned int __must_check normalize_read_threads(unsigned int requested)
{
unsigned int read_threads = requested;
if (read_threads < 1)
read_threads = DEFAULT_VOLUME_READ_THREADS;
if (read_threads > MAX_VOLUME_READ_THREADS)
read_threads = MAX_VOLUME_READ_THREADS;
return read_threads;
}
int uds_make_configuration(const struct uds_parameters *params,
struct configuration **config_ptr)
{
struct configuration *config;
u32 chapters_per_volume = 0;
u32 record_pages_per_chapter = 0;
u32 sparse_chapters_per_volume = 0;
int result;
result = compute_memory_sizes(params->memory_size, params->sparse,
&chapters_per_volume, &record_pages_per_chapter,
&sparse_chapters_per_volume);
if (result != UDS_SUCCESS)
return result;
result = uds_allocate(1, struct configuration, __func__, &config);
if (result != UDS_SUCCESS)
return result;
result = uds_make_geometry(DEFAULT_BYTES_PER_PAGE, record_pages_per_chapter,
chapters_per_volume, sparse_chapters_per_volume, 0, 0,
&config->geometry);
if (result != UDS_SUCCESS) {
uds_free_configuration(config);
return result;
}
config->zone_count = normalize_zone_count(params->zone_count);
config->read_threads = normalize_read_threads(params->read_threads);
config->cache_chapters = DEFAULT_CACHE_CHAPTERS;
config->volume_index_mean_delta = DEFAULT_VOLUME_INDEX_MEAN_DELTA;
config->sparse_sample_rate = (params->sparse ? DEFAULT_SPARSE_SAMPLE_RATE : 0);
config->nonce = params->nonce;
config->bdev = params->bdev;
config->offset = params->offset;
config->size = params->size;
*config_ptr = config;
return UDS_SUCCESS;
}
void uds_free_configuration(struct configuration *config)
{
if (config != NULL) {
uds_free_geometry(config->geometry);
uds_free(config);
}
}
void uds_log_configuration(struct configuration *config)
{
struct geometry *geometry = config->geometry;
uds_log_debug("Configuration:");
uds_log_debug(" Record pages per chapter: %10u", geometry->record_pages_per_chapter);
uds_log_debug(" Chapters per volume: %10u", geometry->chapters_per_volume);
uds_log_debug(" Sparse chapters per volume: %10u", geometry->sparse_chapters_per_volume);
uds_log_debug(" Cache size (chapters): %10u", config->cache_chapters);
uds_log_debug(" Volume index mean delta: %10u", config->volume_index_mean_delta);
uds_log_debug(" Bytes per page: %10zu", geometry->bytes_per_page);
uds_log_debug(" Sparse sample rate: %10u", config->sparse_sample_rate);
uds_log_debug(" Nonce: %llu", (unsigned long long) config->nonce);
}
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_CONFIG_H
#define UDS_CONFIG_H
#include "geometry.h"
#include "io-factory.h"
#include "uds.h"
/*
* The configuration records a variety of parameters used to configure a new UDS index. Some
* parameters are provided by the client, while others are fixed or derived from user-supplied
* values. It is created when an index is created, and it is recorded in the index metadata.
*/
enum {
DEFAULT_VOLUME_INDEX_MEAN_DELTA = 4096,
DEFAULT_CACHE_CHAPTERS = 7,
DEFAULT_SPARSE_SAMPLE_RATE = 32,
MAX_ZONES = 16,
};
/* A set of configuration parameters for the indexer. */
struct configuration {
/* Storage device for the index */
struct block_device *bdev;
/* The maximum allowable size of the index */
size_t size;
/* The offset where the index should start */
off_t offset;
/* Parameters for the volume */
/* The volume layout */
struct geometry *geometry;
/* Index owner's nonce */
u64 nonce;
/* The number of threads used to process index requests */
unsigned int zone_count;
/* The number of threads used to read volume pages */
unsigned int read_threads;
/* Size of the page cache and sparse chapter index cache in chapters */
u32 cache_chapters;
/* Parameters for the volume index */
/* The mean delta for the volume index */
u32 volume_index_mean_delta;
/* Sampling rate for sparse indexing */
u32 sparse_sample_rate;
};
/* On-disk structure of data for a version 8.02 index. */
struct uds_configuration_8_02 {
/* Smaller (16), Small (64) or large (256) indices */
u32 record_pages_per_chapter;
/* Total number of chapters per volume */
u32 chapters_per_volume;
/* Number of sparse chapters per volume */
u32 sparse_chapters_per_volume;
/* Size of the page cache, in chapters */
u32 cache_chapters;
/* Unused field */
u32 unused;
/* The volume index mean delta to use */
u32 volume_index_mean_delta;
/* Size of a page, used for both record pages and index pages */
u32 bytes_per_page;
/* Sampling rate for sparse indexing */
u32 sparse_sample_rate;
/* Index owner's nonce */
u64 nonce;
/* Virtual chapter remapped from physical chapter 0 */
u64 remapped_virtual;
/* New physical chapter which remapped chapter was moved to */
u64 remapped_physical;
} __packed;
/* On-disk structure of data for a version 6.02 index. */
struct uds_configuration_6_02 {
/* Smaller (16), Small (64) or large (256) indices */
u32 record_pages_per_chapter;
/* Total number of chapters per volume */
u32 chapters_per_volume;
/* Number of sparse chapters per volume */
u32 sparse_chapters_per_volume;
/* Size of the page cache, in chapters */
u32 cache_chapters;
/* Unused field */
u32 unused;
/* The volume index mean delta to use */
u32 volume_index_mean_delta;
/* Size of a page, used for both record pages and index pages */
u32 bytes_per_page;
/* Sampling rate for sparse indexing */
u32 sparse_sample_rate;
/* Index owner's nonce */
u64 nonce;
} __packed;
int __must_check uds_make_configuration(const struct uds_parameters *params,
struct configuration **config_ptr);
void uds_free_configuration(struct configuration *config);
int __must_check uds_validate_config_contents(struct buffered_reader *reader,
struct configuration *config);
int __must_check uds_write_config_contents(struct buffered_writer *writer,
struct configuration *config, u32 version);
void uds_log_configuration(struct configuration *config);
#endif /* UDS_CONFIG_H */
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "geometry.h"
#include <linux/compiler.h>
#include <linux/log2.h>
#include "delta-index.h"
#include "errors.h"
#include "logger.h"
#include "memory-alloc.h"
#include "permassert.h"
#include "uds.h"
/*
* An index volume is divided into a fixed number of fixed-size chapters, each consisting of a
* fixed number of fixed-size pages. The volume layout is defined by two constants and four
* parameters. The constants are that index records are 32 bytes long (16-byte block name plus
* 16-byte metadata) and that open chapter index hash slots are one byte long. The four parameters
* are the number of bytes in a page, the number of record pages in a chapter, the number of
* chapters in a volume, and the number of chapters that are sparse. From these parameters, we can
* derive the rest of the layout and other index properties.
*
* The index volume is sized by its maximum memory footprint. For a dense index, the persistent
* storage is about 10 times the size of the memory footprint. For a sparse index, the persistent
* storage is about 100 times the size of the memory footprint.
*
* For a small index with a memory footprint less than 1GB, there are three possible memory
* configurations: 0.25GB, 0.5GB and 0.75GB. The default geometry for each is 1024 index records
* per 32 KB page, 1024 chapters per volume, and either 64, 128, or 192 record pages per chapter
* (resulting in 6, 13, or 20 index pages per chapter) depending on the memory configuration. For
* the VDO default of a 0.25 GB index, this yields a deduplication window of 256 GB using about 2.5
* GB for the persistent storage and 256 MB of RAM.
*
* For a larger index with a memory footprint that is a multiple of 1 GB, the geometry is 1024
* index records per 32 KB page, 256 record pages per chapter, 26 index pages per chapter, and 1024
* chapters for every GB of memory footprint. For a 1 GB volume, this yields a deduplication window
* of 1 TB using about 9GB of persistent storage and 1 GB of RAM.
*
* The above numbers hold for volumes which have no sparse chapters. A sparse volume has 10 times
* as many chapters as the corresponding non-sparse volume, which provides 10 times the
* deduplication window while using 10 times as much persistent storage as the equivalent
* non-sparse volume with the same memory footprint.
*
* If the volume has been converted from a non-lvm format to an lvm volume, the number of chapters
* per volume will have been reduced by one by eliminating physical chapter 0, and the virtual
* chapter that formerly mapped to physical chapter 0 may be remapped to another physical chapter.
* This remapping is expressed by storing which virtual chapter was remapped, and which physical
* chapter it was moved to.
*/
int uds_make_geometry(size_t bytes_per_page,
u32 record_pages_per_chapter,
u32 chapters_per_volume,
u32 sparse_chapters_per_volume,
u64 remapped_virtual,
u64 remapped_physical,
struct geometry **geometry_ptr)
{
int result;
struct geometry *geometry;
result = uds_allocate(1, struct geometry, "geometry", &geometry);
if (result != UDS_SUCCESS)
return result;
geometry->bytes_per_page = bytes_per_page;
geometry->record_pages_per_chapter = record_pages_per_chapter;
geometry->chapters_per_volume = chapters_per_volume;
geometry->sparse_chapters_per_volume = sparse_chapters_per_volume;
geometry->dense_chapters_per_volume = chapters_per_volume - sparse_chapters_per_volume;
geometry->remapped_virtual = remapped_virtual;
geometry->remapped_physical = remapped_physical;
geometry->records_per_page = bytes_per_page / BYTES_PER_RECORD;
geometry->records_per_chapter = geometry->records_per_page * record_pages_per_chapter;
geometry->records_per_volume = (u64) geometry->records_per_chapter * chapters_per_volume;
geometry->chapter_mean_delta = 1 << DEFAULT_CHAPTER_MEAN_DELTA_BITS;
geometry->chapter_payload_bits = bits_per(record_pages_per_chapter - 1);
/*
* We want 1 delta list for every 64 records in the chapter.
* The "| 077" ensures that the chapter_delta_list_bits computation
* does not underflow.
*/
geometry->chapter_delta_list_bits =
bits_per((geometry->records_per_chapter - 1) | 077) - 6;
geometry->delta_lists_per_chapter = 1 << geometry->chapter_delta_list_bits;
/* We need enough address bits to achieve the desired mean delta. */
geometry->chapter_address_bits =
(DEFAULT_CHAPTER_MEAN_DELTA_BITS -
geometry->chapter_delta_list_bits +
bits_per(geometry->records_per_chapter - 1));
geometry->index_pages_per_chapter =
uds_get_delta_index_page_count(geometry->records_per_chapter,
geometry->delta_lists_per_chapter,
geometry->chapter_mean_delta,
geometry->chapter_payload_bits,
bytes_per_page);
geometry->pages_per_chapter = geometry->index_pages_per_chapter + record_pages_per_chapter;
geometry->pages_per_volume = geometry->pages_per_chapter * chapters_per_volume;
geometry->bytes_per_volume =
bytes_per_page * (geometry->pages_per_volume + HEADER_PAGES_PER_VOLUME);
*geometry_ptr = geometry;
return UDS_SUCCESS;
}
int uds_copy_geometry(struct geometry *source, struct geometry **geometry_ptr)
{
return uds_make_geometry(source->bytes_per_page,
source->record_pages_per_chapter,
source->chapters_per_volume,
source->sparse_chapters_per_volume,
source->remapped_virtual, source->remapped_physical,
geometry_ptr);
}
void uds_free_geometry(struct geometry *geometry)
{
uds_free(geometry);
}
u32 __must_check uds_map_to_physical_chapter(const struct geometry *geometry,
u64 virtual_chapter)
{
u64 delta;
if (!uds_is_reduced_geometry(geometry))
return virtual_chapter % geometry->chapters_per_volume;
if (likely(virtual_chapter > geometry->remapped_virtual)) {
delta = virtual_chapter - geometry->remapped_virtual;
if (likely(delta > geometry->remapped_physical))
return delta % geometry->chapters_per_volume;
else
return delta - 1;
}
if (virtual_chapter == geometry->remapped_virtual)
return geometry->remapped_physical;
delta = geometry->remapped_virtual - virtual_chapter;
if (delta < geometry->chapters_per_volume)
return geometry->chapters_per_volume - delta;
/* This chapter is so old the answer doesn't matter. */
return 0;
}
/* Check whether any sparse chapters are in use. */
bool uds_has_sparse_chapters(const struct geometry *geometry, u64 oldest_virtual_chapter,
u64 newest_virtual_chapter)
{
return uds_is_sparse_geometry(geometry) &&
((newest_virtual_chapter - oldest_virtual_chapter + 1) >
geometry->dense_chapters_per_volume);
}
bool uds_is_chapter_sparse(const struct geometry *geometry, u64 oldest_virtual_chapter,
u64 newest_virtual_chapter, u64 virtual_chapter_number)
{
return uds_has_sparse_chapters(geometry, oldest_virtual_chapter,
newest_virtual_chapter) &&
((virtual_chapter_number + geometry->dense_chapters_per_volume) <=
newest_virtual_chapter);
}
/* Calculate how many chapters to expire after opening the newest chapter. */
u32 uds_chapters_to_expire(const struct geometry *geometry, u64 newest_chapter)
{
/* If the index isn't full yet, don't expire anything. */
if (newest_chapter < geometry->chapters_per_volume)
return 0;
/* If a chapter is out of order... */
if (geometry->remapped_physical > 0) {
u64 oldest_chapter = newest_chapter - geometry->chapters_per_volume;
/*
* ... expire an extra chapter when expiring the moved chapter to free physical
* space for the new chapter ...
*/
if (oldest_chapter == geometry->remapped_virtual)
return 2;
/*
* ... but don't expire anything when the new chapter will use the physical chapter
* freed by expiring the moved chapter.
*/
if (oldest_chapter == (geometry->remapped_virtual + geometry->remapped_physical))
return 0;
}
/* Normally, just expire one. */
return 1;
}
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2023 Red Hat
*/
#ifndef UDS_GEOMETRY_H
#define UDS_GEOMETRY_H
#include "uds.h"
/*
* The geometry records parameters that define the layout of a UDS index volume, and the size and
* shape of various index structures. It is created when the index is created, and is referenced by
* many index sub-components.
*/
struct geometry {
/* Size of a chapter page, in bytes */
size_t bytes_per_page;
/* Number of record pages in a chapter */
u32 record_pages_per_chapter;
/* Total number of chapters in a volume */
u32 chapters_per_volume;
/* Number of sparsely-indexed chapters in a volume */
u32 sparse_chapters_per_volume;
/* Number of bits used to determine delta list numbers */
u8 chapter_delta_list_bits;
/* Virtual chapter remapped from physical chapter 0 */
u64 remapped_virtual;
/* New physical chapter where the remapped chapter can be found */
u64 remapped_physical;
/*
* The following properties are derived from the ones above, but they are computed and
* recorded as fields for convenience.
*/
/* Total number of pages in a volume, excluding the header */
u32 pages_per_volume;
/* Total number of bytes in a volume, including the header */
size_t bytes_per_volume;
/* Number of pages in a chapter */
u32 pages_per_chapter;
/* Number of index pages in a chapter index */
u32 index_pages_per_chapter;
/* Number of records that fit on a page */
u32 records_per_page;
/* Number of records that fit in a chapter */
u32 records_per_chapter;
/* Number of records that fit in a volume */
u64 records_per_volume;
/* Number of delta lists per chapter index */
u32 delta_lists_per_chapter;
/* Mean delta for chapter indexes */
u32 chapter_mean_delta;
/* Number of bits needed for record page numbers */
u8 chapter_payload_bits;
/* Number of bits used to compute addresses for chapter delta lists */
u8 chapter_address_bits;
/* Number of densely-indexed chapters in a volume */
u32 dense_chapters_per_volume;
};
enum {
/* The number of bytes in a record (name + metadata) */
BYTES_PER_RECORD = (UDS_RECORD_NAME_SIZE + UDS_RECORD_DATA_SIZE),
/* The default length of a page in a chapter, in bytes */
DEFAULT_BYTES_PER_PAGE = 1024 * BYTES_PER_RECORD,
/* The default maximum number of records per page */
DEFAULT_RECORDS_PER_PAGE = DEFAULT_BYTES_PER_PAGE / BYTES_PER_RECORD,
/* The default number of record pages in a chapter */
DEFAULT_RECORD_PAGES_PER_CHAPTER = 256,
/* The default number of record pages in a chapter for a small index */
SMALL_RECORD_PAGES_PER_CHAPTER = 64,
/* The default number of chapters in a volume */
DEFAULT_CHAPTERS_PER_VOLUME = 1024,
/* The default number of sparsely-indexed chapters in a volume */
DEFAULT_SPARSE_CHAPTERS_PER_VOLUME = 0,
/* The log2 of the default mean delta */
DEFAULT_CHAPTER_MEAN_DELTA_BITS = 16,
/* The log2 of the number of delta lists in a large chapter */
DEFAULT_CHAPTER_DELTA_LIST_BITS = 12,
/* The log2 of the number of delta lists in a small chapter */
SMALL_CHAPTER_DELTA_LIST_BITS = 10,
/* The number of header pages per volume */
HEADER_PAGES_PER_VOLUME = 1,
};
int __must_check uds_make_geometry(size_t bytes_per_page, u32 record_pages_per_chapter,
u32 chapters_per_volume,
u32 sparse_chapters_per_volume, u64 remapped_virtual,
u64 remapped_physical,
struct geometry **geometry_ptr);
int __must_check uds_copy_geometry(struct geometry *source,
struct geometry **geometry_ptr);
void uds_free_geometry(struct geometry *geometry);
u32 __must_check uds_map_to_physical_chapter(const struct geometry *geometry,
u64 virtual_chapter);
/*
* Check whether this geometry is reduced by a chapter. This will only be true if the volume was
* converted from a non-lvm volume to an lvm volume.
*/
static inline bool __must_check uds_is_reduced_geometry(const struct geometry *geometry)
{
return !!(geometry->chapters_per_volume & 1);
}
static inline bool __must_check uds_is_sparse_geometry(const struct geometry *geometry)
{
return geometry->sparse_chapters_per_volume > 0;
}
bool __must_check uds_has_sparse_chapters(const struct geometry *geometry,
u64 oldest_virtual_chapter,
u64 newest_virtual_chapter);
bool __must_check uds_is_chapter_sparse(const struct geometry *geometry,
u64 oldest_virtual_chapter,
u64 newest_virtual_chapter,
u64 virtual_chapter_number);
u32 __must_check uds_chapters_to_expire(const struct geometry *geometry,
u64 newest_chapter);
#endif /* UDS_GEOMETRY_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment