use crate::error::{Error, Result};
pub const DEFAULT_PAGE_SIZE: usize = 64 * 1024;
pub const DEFAULT_NUM_PAGES: usize = 16;
pub const DEFAULT_MUTABLE_FRACTION: f64 = 0.5;
pub const DEFAULT_INITIAL_ADDRESS: u64 = 64;
pub const SECTOR_ALIGNMENT: usize = 4096;
const MAX_PAGE_SIZE: usize = (u32::MAX as usize) + 1;
pub const RO_LAG_BITS: u32 = 20;
pub const RO_LAG_DENOM: u64 = 1 << RO_LAG_BITS;
#[inline]
pub fn ro_lag_num_from_fraction(mutable_fraction: f64) -> u64 {
((1.0 - mutable_fraction) * RO_LAG_DENOM as f64) as u64
}
#[derive(Debug, Clone, PartialEq)]
pub struct HybridLogConfig {
pub page_size: usize,
pub num_pages: usize,
pub mutable_fraction: f64,
pub initial_address: u64,
pub ro_lag_num: u64,
}
impl Default for HybridLogConfig {
fn default() -> Self {
Self {
page_size: DEFAULT_PAGE_SIZE,
num_pages: DEFAULT_NUM_PAGES,
mutable_fraction: DEFAULT_MUTABLE_FRACTION,
initial_address: DEFAULT_INITIAL_ADDRESS,
ro_lag_num: ro_lag_num_from_fraction(DEFAULT_MUTABLE_FRACTION),
}
}
}
impl HybridLogConfig {
pub const DEFAULT: Self = Self {
page_size: DEFAULT_PAGE_SIZE,
num_pages: DEFAULT_NUM_PAGES,
mutable_fraction: DEFAULT_MUTABLE_FRACTION,
initial_address: DEFAULT_INITIAL_ADDRESS,
ro_lag_num: RO_LAG_DENOM / 2,
};
pub fn new(page_size: usize, num_pages: usize, mutable_fraction: f64) -> Result<Self> {
Self::with_initial_address(
page_size,
num_pages,
mutable_fraction,
DEFAULT_INITIAL_ADDRESS,
)
}
pub fn with_initial_address(
page_size: usize,
num_pages: usize,
mutable_fraction: f64,
initial_address: u64,
) -> Result<Self> {
let mut ibuf = itoa::Buffer::new();
if !page_size.is_power_of_two() {
let mut msg = String::from("page_size 必须为 2 的幂,当前为 ");
msg.push_str(ibuf.format(page_size));
return Err(Error::InvalidConfig(msg));
}
if !page_size.is_multiple_of(SECTOR_ALIGNMENT) {
let mut msg = String::from("page_size 必须是 ");
msg.push_str(ibuf.format(SECTOR_ALIGNMENT));
msg.push_str(" 的整数倍,当前为 ");
msg.push_str(ibuf.format(page_size));
return Err(Error::InvalidConfig(msg));
}
if page_size > MAX_PAGE_SIZE {
let mut msg = String::from("page_size 超过上界 ");
msg.push_str(ibuf.format(MAX_PAGE_SIZE));
msg.push_str(",当前为 ");
msg.push_str(ibuf.format(page_size));
return Err(Error::InvalidConfig(msg));
}
if !num_pages.is_power_of_two() {
let mut msg = String::from("num_pages 必须为非零且为 2 的幂,当前为 ");
msg.push_str(ibuf.format(num_pages));
return Err(Error::InvalidConfig(msg));
}
if !(mutable_fraction > 0.0 && mutable_fraction <= 1.0) {
let mut fbuf = zmij::Buffer::new();
let mut msg = String::from("mutable_fraction 必须在 (0.0, 1.0] 区间内,当前为 ");
msg.push_str(fbuf.format(mutable_fraction));
return Err(Error::InvalidConfig(msg));
}
if initial_address < DEFAULT_INITIAL_ADDRESS {
let mut msg = String::from("initial_address 必须 >= ");
msg.push_str(ibuf.format(DEFAULT_INITIAL_ADDRESS));
msg.push_str(",当前为 ");
msg.push_str(ibuf.format(initial_address));
return Err(Error::InvalidConfig(msg));
}
Ok(Self {
page_size,
num_pages,
mutable_fraction,
initial_address,
ro_lag_num: ro_lag_num_from_fraction(mutable_fraction),
})
}
#[inline]
pub const fn page_bits(&self) -> u32 {
self.page_size.trailing_zeros()
}
#[inline]
pub const fn page_mask(&self) -> u64 {
(self.page_size - 1) as u64
}
#[inline]
pub const fn num_pages_mask(&self) -> usize {
self.num_pages - 1
}
#[inline]
pub const fn total_buffer_size(&self) -> usize {
self.page_size * self.num_pages
}
#[inline]
pub const fn page_id(&self, addr: u64) -> u64 {
addr >> self.page_bits()
}
#[inline]
pub const fn page_offset(&self, addr: u64) -> usize {
(addr & self.page_mask()) as usize
}
#[inline]
pub const fn page_start_address(&self, page_id: u64) -> u64 {
page_id << self.page_bits()
}
#[inline]
pub const fn page_slot(&self, addr: u64) -> usize {
((addr >> self.page_bits()) as usize) & self.num_pages_mask()
}
pub const fn calculate_read_only_address(&self, head: u64, tail: u64) -> u64 {
if head >= tail {
return tail;
}
if tail <= self.page_size as u64 {
return head;
}
let memory_span = tail - head;
let ro_offset = ((memory_span as u128 * self.ro_lag_num as u128) >> RO_LAG_BITS) as u64;
let target = head + ro_offset;
let aligned_ro = target & !(self.page_mask());
if aligned_ro <= head {
let head_page = self.page_id(head);
let tail_page = self.page_id(tail);
if (head & self.page_mask()) <= DEFAULT_INITIAL_ADDRESS || head_page == tail_page {
head
} else {
self.page_start_address(head_page + 1)
}
} else if aligned_ro < tail {
aligned_ro
} else {
tail
}
}
}