use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use lazy_static::lazy_static;
use libm::{fabs, sqrt};
use spin::Mutex;
#[derive(Clone, Copy)]
pub struct LearnedThreshold {
pub value: f64,
pub uncertainty: f64,
pub observations: u64,
pub learning_rate: f64,
pub mean_outcome: f64,
pub variance: f64,
}
impl LearnedThreshold {
pub const fn uninformed(initial_guess: f64) -> Self {
Self {
value: initial_guess,
uncertainty: f64::MAX,
observations: 0,
learning_rate: 1.0,
mean_outcome: 0.0,
variance: f64::MAX,
}
}
pub fn observe(&mut self, action_value: f64, outcome_delta_epsilon: f64) {
self.observations += 1;
let n = self.observations as f64;
let delta = outcome_delta_epsilon - self.mean_outcome;
self.mean_outcome += delta / n;
let delta2 = outcome_delta_epsilon - self.mean_outcome;
if self.observations > 1 {
let m2 = self.variance * (n - 2.0) + delta * delta2;
self.variance = m2 / (n - 1.0);
self.uncertainty = sqrt(self.variance / n);
}
let adjustment = if outcome_delta_epsilon < 0.0 {
(action_value - self.value) * self.learning_rate
} else {
(self.value - action_value) * self.learning_rate * 0.5
};
self.value += adjustment;
self.learning_rate = 1.0 / (1.0 + sqrt(self.observations as f64) * 0.1);
}
pub fn confidence(&self) -> f64 {
if self.observations == 0 {
return 0.0;
}
let obs_factor = 1.0 - 1.0 / (1.0 + self.observations as f64 * 0.01);
let unc_factor = 1.0 / (1.0 + fabs(self.uncertainty));
obs_factor * unc_factor
}
pub fn should_act(&self, current_value: f64, estimated_benefit: f64) -> bool {
let benefit_over_uncertainty = estimated_benefit / (self.uncertainty + 1e-10);
current_value >= self.value && benefit_over_uncertainty > 1.0
}
}
#[derive(Clone, Debug)]
pub struct Partition {
pub id: u64,
pub name: alloc::string::String,
pub size_blocks: u64,
pub min_size_blocks: u64,
pub max_size_blocks: u64,
pub access_frequency: f64,
pub avg_latency_us: f64,
pub utilization: f64,
pub last_resize_ms: u64,
}
impl Partition {
pub fn pressure(&self) -> f64 {
let available_capacity =
(self.max_size_blocks - self.size_blocks) as f64 / self.max_size_blocks as f64;
if available_capacity < 0.01 {
return f64::MAX;
}
(self.utilization * self.access_frequency) / available_capacity
}
pub fn epsilon_contribution(&self) -> f64 {
self.avg_latency_us * self.utilization * self.access_frequency
}
}
#[derive(Clone, Copy)]
pub struct RepartitionOutcome {
pub partition_id: u64,
pub old_size_blocks: u64,
pub new_size_blocks: u64,
pub pressure_at_decision: f64,
pub epsilon_before: f64,
pub epsilon_after: f64,
pub time_taken_ms: u64,
}
impl RepartitionOutcome {
pub fn delta_epsilon(&self) -> f64 {
self.epsilon_after - self.epsilon_before
}
pub fn was_beneficial(&self) -> bool {
self.delta_epsilon() < 0.0
}
}
lazy_static! {
pub static ref PARTITION_ENGINE: Mutex<PartitionEngine> = Mutex::new(PartitionEngine::new());
}
pub struct PartitionEngine {
pub partitions: BTreeMap<u64, Partition>,
pub total_pool_blocks: u64,
pub is_repartitioning: bool,
outcomes: alloc::collections::VecDeque<RepartitionOutcome>,
threshold_pressure: LearnedThreshold,
threshold_cooldown: LearnedThreshold,
growth_increment: LearnedThreshold,
shrink_increment: LearnedThreshold,
current_epsilon: f64,
}
impl Default for PartitionEngine {
fn default() -> Self {
Self::new()
}
}
impl PartitionEngine {
pub fn new() -> Self {
Self {
partitions: BTreeMap::new(),
total_pool_blocks: 0,
is_repartitioning: false,
outcomes: alloc::collections::VecDeque::with_capacity(100),
threshold_pressure: LearnedThreshold::uninformed(100.0), threshold_cooldown: LearnedThreshold::uninformed(300_000.0), growth_increment: LearnedThreshold::uninformed(0.1), shrink_increment: LearnedThreshold::uninformed(0.05),
current_epsilon: 0.0,
}
}
pub fn update_epsilon(&mut self, epsilon: f64) {
self.current_epsilon = epsilon;
}
pub fn create_partition(
&mut self,
name: &str,
size_blocks: u64,
min_size_blocks: u64,
max_size_blocks: u64,
) -> Result<u64, &'static str> {
let id = self.partitions.len() as u64;
let partition = Partition {
id,
name: alloc::string::String::from(name),
size_blocks,
min_size_blocks,
max_size_blocks,
access_frequency: 0.0,
avg_latency_us: 0.0,
utilization: 0.0,
last_resize_ms: 0,
};
self.partitions.insert(id, partition);
crate::lcpfs_println!("[ PARTITION] Created partition '{}' ({})", name, id);
Ok(id)
}
pub fn update_stats(
&mut self,
partition_id: u64,
access_frequency: f64,
avg_latency_us: f64,
utilization: f64,
) {
if let Some(partition) = self.partitions.get_mut(&partition_id) {
partition.access_frequency = access_frequency;
partition.avg_latency_us = avg_latency_us;
partition.utilization = utilization;
}
}
pub fn should_repartition(&self, partition_id: u64, current_time_ms: u64) -> bool {
if self.is_repartitioning {
return false;
}
let partition = match self.partitions.get(&partition_id) {
Some(p) => p,
None => return false,
};
let time_since_last = current_time_ms.saturating_sub(partition.last_resize_ms) as f64;
if time_since_last < self.threshold_cooldown.value {
return false;
}
let pressure = partition.pressure();
let benefit = self.estimate_repartition_benefit(partition_id, pressure);
self.threshold_pressure.should_act(pressure, benefit)
&& self.threshold_pressure.confidence() > 0.1
}
fn estimate_repartition_benefit(&self, partition_id: u64, current_pressure: f64) -> f64 {
let similar_outcomes: Vec<_> = self
.outcomes
.iter()
.filter(|o| o.partition_id == partition_id)
.filter(|o| fabs(o.pressure_at_decision - current_pressure) < current_pressure * 0.3)
.collect();
if similar_outcomes.is_empty() {
return current_pressure * 0.1;
}
let beneficial: Vec<_> = similar_outcomes
.iter()
.filter(|o| o.was_beneficial())
.collect();
if beneficial.is_empty() {
return 0.0;
}
let avg_benefit: f64 =
beneficial.iter().map(|o| -o.delta_epsilon()).sum::<f64>() / beneficial.len() as f64;
avg_benefit.max(0.0)
}
pub fn repartition(
&mut self,
partition_id: u64,
current_time_ms: u64,
) -> Result<(), &'static str> {
if self.is_repartitioning {
return Err("Repartition already in progress");
}
let partition = self
.partitions
.get(&partition_id)
.ok_or("Partition not found")?;
let pressure = partition.pressure();
let old_size = partition.size_blocks;
let epsilon_before = self.current_epsilon;
let new_size = if pressure > self.threshold_pressure.value {
let growth = (old_size as f64 * self.growth_increment.value) as u64;
(old_size + growth).min(partition.max_size_blocks)
} else {
let shrink = (old_size as f64 * self.shrink_increment.value) as u64;
(old_size.saturating_sub(shrink)).max(partition.min_size_blocks)
};
if new_size == old_size {
return Ok(()); }
crate::lcpfs_println!(
"[ PARTITION] Resizing partition {} from {} to {} blocks (pressure={:.2})",
partition_id,
old_size,
new_size,
pressure
);
self.is_repartitioning = true;
let start_time = crate::get_time();
let blocks_changed = if new_size > old_size {
let blocks_to_add = new_size - old_size;
self.allocate_blocks(partition_id, blocks_to_add)
} else {
let blocks_to_remove = old_size - new_size;
self.deallocate_blocks(partition_id, blocks_to_remove)
};
let time_taken_ms = (crate::get_time() - start_time) / 1_000_000;
if let Some(p) = self.partitions.get_mut(&partition_id) {
p.size_blocks = new_size;
p.last_resize_ms = current_time_ms;
}
self.is_repartitioning = false;
crate::lcpfs_println!(
"[ PARTITION] Resize complete: {} blocks changed in {} ms",
blocks_changed,
time_taken_ms
);
let outcome = RepartitionOutcome {
partition_id,
old_size_blocks: old_size,
new_size_blocks: new_size,
pressure_at_decision: pressure,
epsilon_before,
epsilon_after: self.current_epsilon,
time_taken_ms,
};
self.learn_from_outcome(&outcome);
self.outcomes.push_back(outcome);
while self.outcomes.len() > 100 {
self.outcomes.pop_front();
}
Ok(())
}
fn learn_from_outcome(&mut self, outcome: &RepartitionOutcome) {
let delta = outcome.delta_epsilon();
self.threshold_pressure
.observe(outcome.pressure_at_decision, delta);
if !outcome.was_beneficial() {
self.threshold_pressure
.observe(outcome.pressure_at_decision * 1.5, 0.0);
}
let size_change_ratio = (outcome.new_size_blocks as f64 - outcome.old_size_blocks as f64)
/ outcome.old_size_blocks as f64;
if size_change_ratio > 0.0 {
self.growth_increment
.observe(size_change_ratio.abs(), delta);
} else {
self.shrink_increment
.observe(size_change_ratio.abs(), delta);
}
}
fn allocate_blocks(&mut self, partition_id: u64, count: u64) -> u64 {
use crate::BLOCK_DEVICES;
let mut allocated = 0u64;
let mut devices = match BLOCK_DEVICES.try_lock() {
Some(d) => d,
None => return 0,
};
if let Some(dev) = devices.get_mut(0) {
let base_block = partition_id * 10000; for i in 0..count.min(100) {
let block_num = (base_block + i) as usize;
let buffer = [0u8; 512];
if dev.write_block(block_num, &buffer).is_ok() {
allocated += 1;
}
}
}
crate::lcpfs_println!(
"[ PARTITION] Allocated {} blocks for partition {}",
allocated,
partition_id
);
allocated
}
fn deallocate_blocks(&mut self, partition_id: u64, count: u64) -> u64 {
let freed = count.min(100);
crate::lcpfs_println!(
"[ PARTITION] Freed {} blocks from partition {}",
freed,
partition_id
);
freed
}
pub fn stats(&self) -> PartitionStats {
let total_partitions = self.partitions.len();
let total_epsilon: f64 = self
.partitions
.values()
.map(|p| p.epsilon_contribution())
.sum();
PartitionStats {
total_partitions,
total_pool_blocks: self.total_pool_blocks,
total_epsilon,
is_repartitioning: self.is_repartitioning,
pressure_threshold: self.threshold_pressure.value,
pressure_confidence: self.threshold_pressure.confidence(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PartitionStats {
pub total_partitions: usize,
pub total_pool_blocks: u64,
pub total_epsilon: f64,
pub is_repartitioning: bool,
pub pressure_threshold: f64,
pub pressure_confidence: f64,
}
pub fn update_epsilon(epsilon: f64) {
PARTITION_ENGINE.lock().update_epsilon(epsilon);
}
pub fn create_partition(
name: &str,
size_blocks: u64,
min_size_blocks: u64,
max_size_blocks: u64,
) -> Result<u64, &'static str> {
PARTITION_ENGINE
.lock()
.create_partition(name, size_blocks, min_size_blocks, max_size_blocks)
}
pub fn update_stats(
partition_id: u64,
access_frequency: f64,
avg_latency_us: f64,
utilization: f64,
) {
PARTITION_ENGINE.lock().update_stats(
partition_id,
access_frequency,
avg_latency_us,
utilization,
);
}
pub fn should_repartition(partition_id: u64, current_time_ms: u64) -> bool {
PARTITION_ENGINE
.lock()
.should_repartition(partition_id, current_time_ms)
}
pub fn repartition(partition_id: u64, current_time_ms: u64) -> Result<(), &'static str> {
PARTITION_ENGINE
.lock()
.repartition(partition_id, current_time_ms)
}
pub fn stats() -> PartitionStats {
PARTITION_ENGINE.lock().stats()
}