use p3_util::log2_ceil_usize;
use super::layout::JaggedLayout;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CellBudget {
live: usize,
provisioned: usize,
}
impl CellBudget {
#[must_use]
pub fn of(layout: &JaggedLayout) -> Self {
Self {
live: layout.area(),
provisioned: layout.dense_capacity(),
}
}
#[must_use]
pub fn stacked(heights: &[usize]) -> Self {
let slots = heights
.iter()
.map(|&height| 1usize << log2_ceil_usize(height.max(1)))
.sum::<usize>();
Self {
live: heights.iter().sum(),
provisioned: slots.max(1).next_power_of_two(),
}
}
#[must_use]
pub const fn live(&self) -> usize {
self.live
}
#[must_use]
pub const fn provisioned(&self) -> usize {
self.provisioned
}
#[must_use]
pub const fn dead(&self) -> usize {
self.provisioned - self.live
}
#[must_use]
pub const fn provisioned_per_live(&self, scale: usize) -> usize {
if self.live == 0 {
return 0;
}
self.provisioned * scale / self.live
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;
use proptest::prelude::*;
use super::*;
use crate::layout::plan_stacked_layout;
use crate::table::TableShape;
fn planner_provisioned(heights: &[usize]) -> usize {
let shapes = heights
.iter()
.map(|&height| TableShape::new(log2_ceil_usize(height.max(1)), 1))
.collect::<Vec<_>>();
1usize << plan_stacked_layout(&shapes).0
}
#[test]
fn a_jagged_envelope_rounds_up_once_and_a_stacking_rounds_up_per_column() {
let heights = [5, 9, 17, 33];
let stacked = CellBudget::stacked(&heights);
assert_eq!(stacked.live(), 64);
assert_eq!(stacked.provisioned(), 128);
assert_eq!(stacked.dead(), 64);
let jagged = CellBudget::of(&JaggedLayout::new(6, &heights).unwrap());
assert_eq!(jagged.live(), 64);
assert_eq!(jagged.provisioned(), 64);
assert_eq!(jagged.dead(), 0);
}
#[test]
fn many_short_columns_are_where_the_two_models_part() {
let heights = vec![3usize; 64];
assert_eq!(CellBudget::stacked(&heights).provisioned(), 256);
assert_eq!(CellBudget::stacked(&heights).dead(), 64);
let jagged = CellBudget::of(&JaggedLayout::new(2, &heights).unwrap());
assert_eq!(jagged.provisioned(), 256);
assert_eq!(jagged.dead(), 64);
}
#[test]
fn a_ratio_is_reported_without_floating_point() {
let budget = CellBudget::of(&JaggedLayout::new(3, &[3, 0, 5, 1]).unwrap());
assert_eq!(budget.provisioned_per_live(1000), 1777);
let empty = CellBudget::of(&JaggedLayout::new(3, &[0, 0]).unwrap());
assert_eq!(empty.provisioned_per_live(1000), 0);
}
proptest! {
#[test]
fn the_stacking_model_agrees_with_the_planner_the_schemes_share(
heights in prop::collection::vec(0usize..=40, 1..=16),
) {
prop_assert_eq!(
CellBudget::stacked(&heights).provisioned(),
planner_provisioned(&heights)
);
}
#[test]
fn a_jagged_envelope_never_provisions_more_than_a_stacking(
heights in prop::collection::vec(0usize..=64, 1..=32),
) {
let mut heights = heights;
heights.resize(heights.len().next_power_of_two(), 0);
let jagged = CellBudget::of(&JaggedLayout::new(6, &heights).unwrap());
let stacked = CellBudget::stacked(&heights);
prop_assert_eq!(jagged.live(), stacked.live());
prop_assert!(jagged.provisioned() <= stacked.provisioned());
}
}
}