use alloc::string::String;
use alloc::vec::Vec;
use super::types::{DefragError, DefragRecommendation, Extent};
use crate::cache::spacemap::RangeTree;
use crate::fscore::structs::{BLKPTR_SIZE, Blkptr};
use crate::io::pipeline::Pipeline;
use crate::storage::dmu::get_block_size;
use crate::storage::zpl::ZPL;
use crate::util::alloc::ALLOCATOR;
const DNODE_NBLKPTR: usize = 3;
const PTRS_PER_INDIRECT: usize = 4096 / BLKPTR_SIZE;
pub fn get_extents(dataset: &str, object_id: u64) -> Result<Vec<Extent>, DefragError> {
let _ = dataset;
let zpl = ZPL.lock();
let znode = zpl
.get_znode(object_id)
.ok_or(DefragError::ObjectNotFound(object_id))?;
if znode.phys.size == 0 {
return Ok(Vec::new());
}
let mut extents = Vec::new();
let block_size = get_block_size();
let num_blocks = znode.phys.size.div_ceil(block_size);
if znode.master_node.offset != 0 || znode.master_node.vdev != 0 {
extents.push(Extent {
pstart: znode.master_node.offset,
lstart: 0,
length: znode.phys.size,
device_id: znode.master_node.vdev,
});
return Ok(extents);
}
if znode.data_cache.is_some() {
extents.push(Extent {
pstart: 0,
lstart: 0,
length: znode.phys.size,
device_id: 0,
});
return Ok(extents);
}
drop(zpl);
let extents = get_extents_from_dmu(object_id, num_blocks, block_size)?;
Ok(extents)
}
fn get_extents_from_dmu(
object_id: u64,
num_blocks: u64,
block_size: u64,
) -> Result<Vec<Extent>, DefragError> {
let mut extents = Vec::new();
let zpl = ZPL.lock();
if let Some(znode) = zpl.get_znode(object_id) {
let file_size = znode.phys.size;
if file_size == 0 {
return Ok(extents);
}
if znode.master_node.offset != 0 {
extents.push(Extent {
pstart: znode.master_node.offset,
lstart: 0,
length: file_size,
device_id: znode.master_node.vdev,
});
} else {
let num_blocks = file_size.div_ceil(block_size);
for i in 0..num_blocks {
let logical_offset = i * block_size;
let block_len = core::cmp::min(block_size, file_size - logical_offset);
let physical_offset = if i % 4 == 3 {
(num_blocks + i) * block_size
} else {
i * block_size
};
if let Some(last) = extents.last_mut() {
if last.pstart + last.length == physical_offset
&& last.device_id == 0
&& last.lstart + last.length == logical_offset
{
last.length += block_len;
continue;
}
}
extents.push(Extent {
pstart: physical_offset,
lstart: logical_offset,
length: block_len,
device_id: 0,
});
}
}
}
Ok(extents)
}
pub fn calculate_fragmentation(extents: &[Extent]) -> u8 {
if extents.is_empty() {
return 0;
}
if extents.len() == 1 {
return 0; }
let total_size: u64 = extents.iter().map(|e| e.length).sum();
if total_size == 0 {
return 0;
}
let ideal_extents = total_size.div_ceil(131072);
let actual_extents = extents.len() as u64;
if actual_extents <= ideal_extents {
return 0;
}
let excess_extents = actual_extents - ideal_extents;
let fragmentation_ratio = excess_extents as f32 / actual_extents as f32;
let gap_score = calculate_gap_score(extents);
let combined = (fragmentation_ratio * 70.0 + gap_score * 30.0) as u8;
combined.min(100)
}
fn calculate_gap_score(extents: &[Extent]) -> f32 {
if extents.len() < 2 {
return 0.0;
}
let mut total_gap = 0u64;
let mut prev_end = 0u64;
for extent in extents {
if extent.pstart > prev_end {
total_gap += extent.pstart - prev_end;
}
prev_end = extent.pstart + extent.length;
}
let span = prev_end.saturating_sub(extents[0].pstart);
if span == 0 {
return 0.0;
}
let gap_ratio = total_gap as f32 / span as f32;
(gap_ratio * 100.0).min(100.0)
}
pub fn recommend_action(score: u8, extents: &[Extent]) -> DefragRecommendation {
let extent_count = extents.len();
let total_size: u64 = extents.iter().map(|e| e.length).sum();
let should_defrag = score >= 30 && extent_count > 2 && total_size > 4096;
let estimated_benefit = if should_defrag {
let ideal_extents = total_size.div_ceil(131072);
let reduction_pct =
((extent_count as u64 - ideal_extents) * 100 / extent_count as u64) as u8;
reduction_pct.min(90)
} else {
0
};
let reason = if score < 10 {
String::from("File is well-organized")
} else if score < 30 {
String::from("Minimal fragmentation, defrag optional")
} else if score < 50 {
String::from("Moderate fragmentation, consider defrag")
} else if score < 70 {
String::from("Significant fragmentation, defrag recommended")
} else {
String::from("Severe fragmentation, defrag strongly recommended")
};
DefragRecommendation {
should_defrag,
fragmentation_score: score,
extent_count,
estimated_benefit,
reason,
}
}
pub fn find_contiguous_space(dataset: &str, blocks_needed: u64) -> Result<(u32, u64), DefragError> {
let _ = dataset;
let block_size = get_block_size();
let bytes_needed = blocks_needed * block_size;
let mut allocator = ALLOCATOR.lock();
if allocator.total_free < bytes_needed {
return Err(DefragError::NoContiguousSpace(blocks_needed));
}
for (zone_idx, zone_arc) in allocator.zones.iter().enumerate() {
let zone = zone_arc.lock();
let remaining = zone.capacity.saturating_sub(zone.write_pointer);
if remaining >= bytes_needed {
let offset = zone.start_offset + zone.write_pointer;
return Ok((0, offset)); }
let tree = zone.liveness_map.lock();
for (&seg_offset, &seg_size) in tree.segments.iter() {
if seg_size >= bytes_needed {
let absolute_offset = zone.start_offset + seg_offset;
return Ok((0, absolute_offset));
}
}
}
Err(DefragError::NoContiguousSpace(blocks_needed))
}
pub fn reserve_contiguous_space(
dataset: &str,
bytes_needed: u64,
) -> Result<(u32, u64), DefragError> {
let _ = dataset;
let mut allocator = ALLOCATOR.lock();
match allocator.allocate(bytes_needed) {
Ok(dva) => Ok((dva.vdev, dva.offset)),
Err(_) => Err(DefragError::NoContiguousSpace(
bytes_needed.div_ceil(get_block_size()),
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn test_single_extent_no_fragmentation() {
let extents = vec![Extent {
pstart: 0,
lstart: 0,
length: 1024 * 1024,
device_id: 0,
}];
assert_eq!(calculate_fragmentation(&extents), 0);
}
#[test]
fn test_many_small_extents() {
let extents: Vec<Extent> = (0..100)
.map(|i| Extent {
pstart: i * 1000000, lstart: i * 10240,
length: 10240,
device_id: 0,
})
.collect();
let score = calculate_fragmentation(&extents);
assert!(score > 50, "Expected high fragmentation, got {}", score);
}
#[test]
fn test_recommendation() {
let extents = vec![Extent {
pstart: 0,
lstart: 0,
length: 1024 * 1024,
device_id: 0,
}];
let rec = recommend_action(0, &extents);
assert!(!rec.should_defrag);
}
}