Skip to main content

linkrs_storage/edge/mutable_csr/
stats.rs

1use super::super::{ColdStamps, HotNbr};
2use super::MutableCsr;
3use crate::edge::FragmentationStats;
4
5impl MutableCsr {
6    pub fn overflow_chunk_allocs(&self) -> u64 {
7        self.overflow_chunk_allocs
8    }
9
10    pub fn primary_block_allocs(&self) -> u64 {
11        self.primary_block_allocs
12    }
13
14    pub fn repack_count(&self) -> u64 {
15        self.repack_count
16    }
17
18    pub fn tombstone_reuse_count(&self) -> u64 {
19        self.tombstone_reuse_count
20    }
21
22    pub fn tombstone_reuse_hint_hits(&self) -> u64 {
23        self.tombstone_reuse_hint_hits
24    }
25
26    pub fn tombstone_reuse_hint_misses(&self) -> u64 {
27        self.tombstone_reuse_hint_misses
28    }
29
30    pub fn tombstone_reuse_hint_hit_rate(&self) -> f32 {
31        let hits = self.tombstone_reuse_hint_hits as f64;
32        let misses = self.tombstone_reuse_hint_misses as f64;
33        if hits + misses == 0.0 {
34            0.0
35        } else {
36            (hits / (hits + misses)) as f32
37        }
38    }
39
40    pub fn live_set_rebuild_count(&self) -> u64 {
41        self.live_set_rebuild_count
42    }
43
44    pub fn reset_baseline_counters(&mut self) {
45        self.overflow_chunk_allocs = 0;
46        self.primary_block_allocs = 0;
47        self.repack_count = 0;
48        self.tombstone_reuse_count = 0;
49        self.live_set_rebuild_count = 0;
50        self.tombstone_reuse_hint_hits = 0;
51        self.tombstone_reuse_hint_misses = 0;
52    }
53}
54
55impl MutableCsr {
56    /// Get used memory size, counting reserved topology plus indexes.
57    ///
58    /// Covers the primary neighbor list, the offset/degree/capacity arrays,
59    /// the per-row order flags, reserved overflow chunk buffers, the dedup
60    /// live sets and this struct.
61    /// Tombstone authority memory is accounted by the table layer through
62    /// the shared tombstone estimate so both stay on one caliber.
63    pub fn used_memory_size(&self) -> usize {
64        let slot_bytes = std::mem::size_of::<HotNbr>() + std::mem::size_of::<ColdStamps>();
65        let arrays = self.hot_list.capacity() * std::mem::size_of::<HotNbr>()
66            + self.cold_list.capacity() * std::mem::size_of::<ColdStamps>()
67            + self.rows.adj_offsets.capacity() * std::mem::size_of::<u32>()
68            + self.rows.degrees.capacity() * std::mem::size_of::<u32>()
69            + self.rows.primary_capacities.capacity() * std::mem::size_of::<u32>()
70            + self.primary_sorted.capacity() * std::mem::size_of::<bool>();
71        let overflow_reserved: usize = self
72            .overflow_chunks
73            .iter()
74            .map(|(_, chunks)| chunks.iter().map(|chunk| chunk.capacity()).sum::<usize>())
75            .sum();
76        let live_heap: usize = self.live_sets.heap_bytes_total();
77        let live_sets = self.live_sets.index_bytes() + live_heap;
78        arrays
79            + overflow_reserved * slot_bytes
80            + self.overflow_chunks.index_bytes()
81            + live_sets
82            + std::mem::size_of::<Self>()
83    }
84
85    /// Compute fragmentation ratio as wasted share of reserved capacity.
86    ///
87    /// Single caliber shared with `FragmentationStats`: `wasted / total`,
88    /// ranging from 0.0 (perfect packing) to below 1.0. A ratio above 0.5
89    /// marks a waste-dominated table worth a group merge; the write path
90    /// triggers on per-vertex reclaimable counts instead of this ratio.
91    pub fn fragmentation_ratio(&self) -> f32 {
92        if self.total_edge_capacity == 0 {
93            return 0.0;
94        }
95        let active_edges = self.edge_count as usize;
96        self.total_edge_capacity.saturating_sub(active_edges) as f32
97            / self.total_edge_capacity as f32
98    }
99
100    /// Estimate wasted memory due to fragmentation (in bytes)
101    pub(crate) fn wasted_bytes_estimate(&self) -> usize {
102        let active_edges = self.edge_count as usize;
103        let slot_bytes = std::mem::size_of::<HotNbr>() + std::mem::size_of::<ColdStamps>();
104        self.total_edge_capacity.saturating_sub(active_edges) * slot_bytes
105    }
106
107    /// Get detailed fragmentation statistics.
108    ///
109    /// Both counters derive from the live structures: dead entries are the
110    /// physically stored entries minus live edges (primary tombstones plus
111    /// overflow dead entries), and wasted capacity is the reserved capacity
112    /// minus live edges (row gaps plus tombstone slots).
113    pub fn get_fragmentation_stats(&self) -> FragmentationStats {
114        let live_edges = self.edge_count as usize;
115
116        let mut physical_entries = 0usize;
117        for vid in 0..self.vertex_capacity() {
118            physical_entries += self.rows.degrees[vid] as usize;
119        }
120        for (_, chunks) in self.overflow_chunks.iter() {
121            for chunk in chunks {
122                physical_entries += chunk.len();
123            }
124        }
125
126        let dead_entries = physical_entries.saturating_sub(live_edges);
127        let wasted_capacity = self.total_edge_capacity.saturating_sub(live_edges);
128
129        FragmentationStats::with_dead_info(
130            self.total_edge_capacity,
131            live_edges,
132            dead_entries,
133            wasted_capacity,
134        )
135    }
136}