use crate::fscore::structs::Dva;
use alloc::collections::{BTreeMap, VecDeque};
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(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
#[repr(u8)]
pub enum StorageClass {
Gold = 0,
#[default]
Silver = 1,
Bronze = 2,
}
impl StorageClass {
pub fn speed_factor(&self) -> f64 {
match self {
StorageClass::Gold => 10.0, StorageClass::Silver => 3.0, StorageClass::Bronze => 1.0, }
}
pub fn cost_factor(&self) -> f64 {
match self {
StorageClass::Gold => 5.0, StorageClass::Silver => 2.0, StorageClass::Bronze => 1.0, }
}
pub fn promote(&self) -> Option<StorageClass> {
match self {
StorageClass::Gold => None,
StorageClass::Silver => Some(StorageClass::Gold),
StorageClass::Bronze => Some(StorageClass::Silver),
}
}
pub fn demote(&self) -> Option<StorageClass> {
match self {
StorageClass::Gold => Some(StorageClass::Silver),
StorageClass::Silver => Some(StorageClass::Bronze),
StorageClass::Bronze => None,
}
}
}
#[derive(Clone, Copy, Default)]
pub struct AccessStats {
pub object_id: u64,
pub current_tier: StorageClass,
pub access_count: u64,
pub read_count: u64,
pub write_count: u64,
pub last_access_ms: u64,
pub creation_ms: u64,
pub size_bytes: u64,
pub access_frequency: f64,
pub idle_time_ms: u64,
}
impl AccessStats {
pub fn heat_score(&self) -> f64 {
let frequency_score = self.access_frequency;
let recency_score = if self.idle_time_ms > 0 {
1.0 / (1.0 + (self.idle_time_ms as f64 / 3_600_000.0)) } else {
1.0
};
frequency_score * recency_score
}
}
#[derive(Clone, Copy)]
pub struct TierObservation {
pub timestamp_ms: u64,
pub object_id: u64,
pub before_tier: StorageClass,
pub after_tier: StorageClass,
pub heat_at_decision: f64,
pub epsilon_before: f64,
pub epsilon_after: f64,
}
impl TierObservation {
pub fn delta_epsilon(&self) -> f64 {
self.epsilon_after - self.epsilon_before
}
pub fn was_promotion(&self) -> bool {
(self.before_tier as u8) > (self.after_tier as u8)
}
pub fn was_demotion(&self) -> bool {
(self.before_tier as u8) < (self.after_tier as u8)
}
}
lazy_static! {
pub static ref TIER_MANAGER: Mutex<TierManager> = Mutex::new(TierManager::new());
}
pub struct TierManager {
pub vdev_map: [StorageClass; 8],
access_stats: BTreeMap<u64, AccessStats>,
observations: VecDeque<TierObservation>,
threshold_promote: LearnedThreshold,
threshold_demote: LearnedThreshold,
min_observations: LearnedThreshold,
migration_cooldown_ms: LearnedThreshold,
frequency_decay: LearnedThreshold,
size_threshold: LearnedThreshold,
current_epsilon: f64,
last_migration: BTreeMap<u64, u64>,
}
impl Default for TierManager {
fn default() -> Self {
Self::new()
}
}
impl TierManager {
pub fn new() -> Self {
let mut vdev_map = [StorageClass::Silver; 8];
vdev_map[0] = StorageClass::Gold; vdev_map[1] = StorageClass::Gold; vdev_map[2] = StorageClass::Silver; vdev_map[3] = StorageClass::Silver;
vdev_map[4] = StorageClass::Bronze; vdev_map[5] = StorageClass::Bronze;
vdev_map[6] = StorageClass::Bronze;
vdev_map[7] = StorageClass::Bronze;
Self {
vdev_map,
access_stats: BTreeMap::new(),
observations: VecDeque::with_capacity(1000),
threshold_promote: LearnedThreshold::uninformed(0.7), threshold_demote: LearnedThreshold::uninformed(0.1), min_observations: LearnedThreshold::uninformed(10.0), migration_cooldown_ms: LearnedThreshold::uninformed(3_600_000.0), frequency_decay: LearnedThreshold::uninformed(0.95), size_threshold: LearnedThreshold::uninformed(4096.0),
current_epsilon: 0.0,
last_migration: BTreeMap::new(),
}
}
pub fn update_epsilon(&mut self, epsilon: f64) {
self.current_epsilon = epsilon;
}
pub fn record_access(&mut self, object_id: u64, is_write: bool, timestamp_ms: u64, size: u64) {
let decay = self.frequency_decay.value;
let stats = self
.access_stats
.entry(object_id)
.or_insert_with(|| AccessStats {
object_id,
current_tier: StorageClass::Silver,
access_count: 0,
read_count: 0,
write_count: 0,
last_access_ms: timestamp_ms,
creation_ms: timestamp_ms,
size_bytes: size,
access_frequency: 0.0,
idle_time_ms: 0,
});
stats.access_count += 1;
if is_write {
stats.write_count += 1;
} else {
stats.read_count += 1;
}
stats.idle_time_ms = timestamp_ms.saturating_sub(stats.last_access_ms);
stats.last_access_ms = timestamp_ms;
stats.access_frequency = stats.access_frequency * decay + (1.0 - decay);
}
pub fn decay_frequencies(&mut self) {
let decay = self.frequency_decay.value;
for stats in self.access_stats.values_mut() {
stats.access_frequency *= decay;
}
}
pub fn select_vdev(&self, policy: AllocationPolicy) -> usize {
for (id, class) in self.vdev_map.iter().enumerate() {
if *class == policy.intent {
return id;
}
}
0
}
pub fn check_migration(
&self,
object_id: u64,
current_time_ms: u64,
) -> Option<MigrationDecision> {
let stats = self.access_stats.get(&object_id)?;
if let Some(&last_mig) = self.last_migration.get(&object_id) {
if current_time_ms < last_mig + self.migration_cooldown_ms.value as u64 {
return None;
}
}
if (stats.access_count as f64) < self.min_observations.value {
return None;
}
if (stats.size_bytes as f64) < self.size_threshold.value {
return None;
}
let heat = stats.heat_score();
if let Some(target_tier) = stats.current_tier.promote() {
let benefit = self.estimate_promotion_benefit(stats, target_tier);
if self.threshold_promote.should_act(heat, benefit)
&& self.threshold_promote.confidence() > 0.1
{
return Some(MigrationDecision::Promote {
object_id,
from_tier: stats.current_tier,
to_tier: target_tier,
estimated_benefit: benefit,
});
}
}
if let Some(target_tier) = stats.current_tier.demote() {
let benefit = self.estimate_demotion_benefit(stats, target_tier);
if heat < self.threshold_demote.value
&& benefit > self.threshold_demote.uncertainty
&& self.threshold_demote.confidence() > 0.1
{
return Some(MigrationDecision::Demote {
object_id,
from_tier: stats.current_tier,
to_tier: target_tier,
estimated_benefit: benefit,
});
}
}
None
}
fn estimate_promotion_benefit(&self, stats: &AccessStats, target: StorageClass) -> f64 {
let speed_improvement = target.speed_factor() / stats.current_tier.speed_factor();
let access_savings = stats.access_frequency * speed_improvement * 10.0;
let migration_cost = stats.size_bytes as f64 / 1_000_000.0; let storage_cost = (target.cost_factor() - stats.current_tier.cost_factor())
* stats.size_bytes as f64
/ 1_000_000_000.0;
access_savings - migration_cost - storage_cost
}
fn estimate_demotion_benefit(&self, stats: &AccessStats, target: StorageClass) -> f64 {
let storage_savings = (stats.current_tier.cost_factor() - target.cost_factor())
* stats.size_bytes as f64
/ 1_000_000_000.0;
let access_penalty = stats.access_frequency * 5.0;
storage_savings - access_penalty
}
pub fn execute_migration(&mut self, decision: &MigrationDecision, current_time_ms: u64) {
let (object_id, from_tier, to_tier) = match decision {
MigrationDecision::Promote {
object_id,
from_tier,
to_tier,
..
} => (*object_id, *from_tier, *to_tier),
MigrationDecision::Demote {
object_id,
from_tier,
to_tier,
..
} => (*object_id, *from_tier, *to_tier),
};
let epsilon_before = self.current_epsilon;
let start_time = crate::get_time();
let blocks_moved = Self::relocate_object_to_tier(object_id, from_tier, to_tier);
let time_taken_ms = (crate::get_time() - start_time) / 1_000_000;
crate::lcpfs_println!(
"[ TIER ] Migrated object {} from {:?} to {:?} ({} blocks, {} ms)",
object_id,
from_tier,
to_tier,
blocks_moved,
time_taken_ms
);
if let Some(stats) = self.access_stats.get_mut(&object_id) {
stats.current_tier = to_tier;
}
let observation = TierObservation {
timestamp_ms: current_time_ms,
object_id,
before_tier: from_tier,
after_tier: to_tier,
heat_at_decision: self
.access_stats
.get(&object_id)
.map(|s| s.heat_score())
.unwrap_or(0.0),
epsilon_before,
epsilon_after: self.current_epsilon, };
self.observations.push_back(observation);
while self.observations.len() > 1000 {
self.observations.pop_front();
}
self.last_migration.insert(object_id, current_time_ms);
}
pub fn learn_from_outcomes(&mut self) {
for obs in self.observations.iter() {
let delta = obs.delta_epsilon();
if obs.was_promotion() {
self.threshold_promote.observe(obs.heat_at_decision, delta);
} else if obs.was_demotion() {
self.threshold_demote.observe(obs.heat_at_decision, delta);
}
}
}
fn relocate_object_to_tier(
object_id: u64,
from_tier: StorageClass,
to_tier: StorageClass,
) -> u64 {
use crate::BLOCK_DEVICES;
use alloc::vec;
let mut blocks_moved = 0u64;
let from_dev = match from_tier {
StorageClass::Gold => 0,
StorageClass::Silver => 1,
StorageClass::Bronze => 2,
};
let to_dev = match to_tier {
StorageClass::Gold => 0,
StorageClass::Silver => 1,
StorageClass::Bronze => 2,
};
let mut devices = match BLOCK_DEVICES.try_lock() {
Some(d) => d,
None => return 0,
};
let object_blocks = 20u64;
let base_block = object_id * 100;
for i in 0..object_blocks {
let mut buffer = vec![0u8; 512];
let block_id = (base_block + i) as usize;
let read_ok = if let Some(src) = devices.get_mut(from_dev) {
src.read_block(block_id, &mut buffer).is_ok()
} else {
false
};
if !read_ok {
continue;
}
let write_ok = if let Some(dst) = devices.get_mut(to_dev) {
dst.write_block(block_id, &buffer).is_ok()
} else {
false
};
if write_ok {
blocks_moved += 1;
}
}
blocks_moved
}
pub fn stats(&self) -> TierStats {
let mut gold_count = 0u64;
let mut silver_count = 0u64;
let mut bronze_count = 0u64;
for stats in self.access_stats.values() {
match stats.current_tier {
StorageClass::Gold => gold_count += 1,
StorageClass::Silver => silver_count += 1,
StorageClass::Bronze => bronze_count += 1,
}
}
TierStats {
total_objects: self.access_stats.len() as u64,
gold_tier_objects: gold_count,
silver_tier_objects: silver_count,
bronze_tier_objects: bronze_count,
promote_threshold: self.threshold_promote.value,
promote_confidence: self.threshold_promote.confidence(),
demote_threshold: self.threshold_demote.value,
demote_confidence: self.threshold_demote.confidence(),
}
}
pub fn check_migration_dva(&self, dva: Dva, access_count: u64) -> Option<usize> {
let current_class = self.vdev_map[dva.vdev as usize];
let object_id = dva.offset;
let stats = AccessStats {
object_id,
current_tier: current_class,
access_count,
read_count: access_count,
write_count: 0,
last_access_ms: 0,
creation_ms: 0,
size_bytes: 4096,
access_frequency: access_count as f64 / 100.0,
idle_time_ms: 0,
};
let heat = stats.heat_score();
if current_class == StorageClass::Gold && heat < self.threshold_demote.value {
for (id, class) in self.vdev_map.iter().enumerate() {
if *class == StorageClass::Silver {
crate::lcpfs_println!(
"[ TIER ] Block {:?} is COLD (heat={:.2}). Demoting to Silver.",
dva,
heat
);
return Some(id);
}
}
}
if current_class == StorageClass::Silver && heat > self.threshold_promote.value {
for (id, class) in self.vdev_map.iter().enumerate() {
if *class == StorageClass::Gold {
crate::lcpfs_println!(
"[ TIER ] Block {:?} is HOT (heat={:.2}). Promoting to Gold.",
dva,
heat
);
return Some(id);
}
}
}
None
}
}
#[derive(Debug, Clone, Copy)]
pub enum MigrationDecision {
Promote {
object_id: u64,
from_tier: StorageClass,
to_tier: StorageClass,
estimated_benefit: f64,
},
Demote {
object_id: u64,
from_tier: StorageClass,
to_tier: StorageClass,
estimated_benefit: f64,
},
}
pub struct AllocationPolicy {
pub intent: StorageClass,
}
#[derive(Debug, Clone, Copy)]
pub struct TierStats {
pub total_objects: u64,
pub gold_tier_objects: u64,
pub silver_tier_objects: u64,
pub bronze_tier_objects: u64,
pub promote_threshold: f64,
pub promote_confidence: f64,
pub demote_threshold: f64,
pub demote_confidence: f64,
}
pub fn update_epsilon(epsilon: f64) {
TIER_MANAGER.lock().update_epsilon(epsilon);
}
pub fn record_access(object_id: u64, is_write: bool, timestamp_ms: u64, size: u64) {
TIER_MANAGER
.lock()
.record_access(object_id, is_write, timestamp_ms, size);
}
pub fn check_migration(object_id: u64, current_time_ms: u64) -> Option<MigrationDecision> {
TIER_MANAGER
.lock()
.check_migration(object_id, current_time_ms)
}
pub fn execute_migration(decision: &MigrationDecision, current_time_ms: u64) {
TIER_MANAGER
.lock()
.execute_migration(decision, current_time_ms);
}
pub fn stats() -> TierStats {
TIER_MANAGER.lock().stats()
}
pub fn select_vdev(policy: AllocationPolicy) -> usize {
TIER_MANAGER.lock().select_vdev(policy)
}