pub const GROUP_FRAGMENTATION_THRESHOLD: f32 = 0.5;
#[derive(Debug, Clone, Copy)]
pub struct FragmentationStats {
pub total_capacity: usize,
pub reachable_edges: usize,
pub dead_entries: usize,
pub wasted_capacity: usize,
}
impl FragmentationStats {
pub fn with_dead_info(
total_capacity: usize,
reachable_edges: usize,
dead_entries: usize,
wasted_capacity: usize,
) -> Self {
Self {
total_capacity,
reachable_edges,
dead_entries,
wasted_capacity,
}
}
pub fn fragmentation_ratio(&self) -> f32 {
if self.total_capacity == 0 {
0.0
} else {
self.wasted_capacity as f32 / self.total_capacity as f32
}
}
pub fn measured_bytes_per_edge(used_bytes: usize, edges: u64) -> usize {
if edges == 0 {
return 0;
}
let bpe = used_bytes / edges as usize;
if bpe == 0 {
log::debug!(
"bytes_per_edge: measured zero ({} bytes / {} edges), reporting zero",
used_bytes,
edges,
);
}
bpe
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VertexFragmentation {
pub vertex: u32,
pub live_edges: usize,
pub dead_entries: usize,
pub capacity: usize,
pub reclaimable: usize,
}
impl VertexFragmentation {
pub fn waste(&self) -> usize {
self.capacity.saturating_sub(self.live_edges)
}
pub fn density(&self) -> f32 {
if self.capacity == 0 {
1.0
} else {
self.live_edges as f32 / self.capacity as f32
}
}
pub fn needs_compact(&self) -> bool {
self.reclaimable > 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fragmentation_stats_no_fragmentation() {
let stats = FragmentationStats {
total_capacity: 100,
reachable_edges: 100,
dead_entries: 0,
wasted_capacity: 0,
};
assert_eq!(stats.fragmentation_ratio(), 0.0);
assert!(stats.fragmentation_ratio() < GROUP_FRAGMENTATION_THRESHOLD);
}
#[test]
fn test_fragmentation_stats_with_fragmentation() {
let stats = FragmentationStats {
total_capacity: 100,
reachable_edges: 30,
dead_entries: 2,
wasted_capacity: 50,
};
assert_eq!(stats.fragmentation_ratio(), 0.5);
assert!(stats.fragmentation_ratio() >= GROUP_FRAGMENTATION_THRESHOLD);
}
#[test]
fn test_fragmentation_stats_severe_fragmentation() {
let stats = FragmentationStats {
total_capacity: 100,
reachable_edges: 20,
dead_entries: 3,
wasted_capacity: 80,
};
assert_eq!(stats.fragmentation_ratio(), 0.8);
assert!(stats.fragmentation_ratio() >= GROUP_FRAGMENTATION_THRESHOLD);
}
#[test]
fn test_fragmentation_stats_empty() {
let stats = FragmentationStats::with_dead_info(0, 0, 0, 0);
assert_eq!(stats.fragmentation_ratio(), 0.0);
}
#[test]
fn test_fragmentation_ratio_threshold() {
let stats = FragmentationStats {
total_capacity: 100,
reachable_edges: 50,
dead_entries: 1,
wasted_capacity: 60,
};
assert!(stats.fragmentation_ratio() >= GROUP_FRAGMENTATION_THRESHOLD);
assert!(stats.fragmentation_ratio() <= 1.0);
}
#[test]
fn test_measured_bytes_per_edge() {
assert_eq!(FragmentationStats::measured_bytes_per_edge(0, 0), 0);
assert_eq!(FragmentationStats::measured_bytes_per_edge(100, 0), 0);
assert_eq!(FragmentationStats::measured_bytes_per_edge(100, 10), 10);
assert_eq!(FragmentationStats::measured_bytes_per_edge(5, 10), 0);
}
#[test]
fn test_vertex_fragmentation_view() {
let view = VertexFragmentation {
vertex: 3,
live_edges: 4,
dead_entries: 1,
capacity: 8,
reclaimable: 1,
};
assert_eq!(view.waste(), 4);
assert_eq!(view.density(), 0.5);
assert!(view.needs_compact());
let clean = VertexFragmentation {
vertex: 4,
live_edges: 4,
dead_entries: 0,
capacity: 8,
reclaimable: 0,
};
assert!(!clean.needs_compact());
}
}