memscope_rs/smart_pointers/
tracker.rs

1use std::collections::HashMap;
2use std::sync::atomic::{AtomicUsize, Ordering};
3use std::time::Instant;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6pub enum PointerType {
7    Box,
8    Rc,
9    Arc,
10    Weak,
11    RefCell,
12    Mutex,
13    RwLock,
14}
15
16#[derive(Debug, Clone)]
17pub struct PointerInfo {
18    pub ptr_type: PointerType,
19    pub allocation_size: usize,
20    pub created_at: Instant,
21    pub ref_count: Option<usize>,
22    pub inner_type: String,
23    pub allocation_id: u64,
24}
25
26pub struct SmartPointerTracker {
27    active_pointers: HashMap<usize, PointerInfo>,
28    allocation_counter: AtomicUsize,
29    type_stats: HashMap<PointerType, TypeStats>,
30}
31
32#[derive(Debug, Default)]
33pub struct TypeStats {
34    pub total_count: usize,
35    pub current_count: usize,
36    pub total_size: usize,
37    pub current_size: usize,
38    pub max_concurrent: usize,
39}
40
41impl SmartPointerTracker {
42    pub fn new() -> Self {
43        Self {
44            active_pointers: HashMap::new(),
45            allocation_counter: AtomicUsize::new(0),
46            type_stats: HashMap::new(),
47        }
48    }
49
50    pub fn track_allocation(
51        &mut self,
52        ptr_addr: usize,
53        ptr_type: PointerType,
54        size: usize,
55        inner_type: String,
56        ref_count: Option<usize>,
57    ) -> u64 {
58        let allocation_id = self.allocation_counter.fetch_add(1, Ordering::Relaxed) as u64;
59
60        let info = PointerInfo {
61            ptr_type: ptr_type.clone(),
62            allocation_size: size,
63            created_at: Instant::now(),
64            ref_count,
65            inner_type,
66            allocation_id,
67        };
68
69        self.active_pointers.insert(ptr_addr, info);
70        self.update_type_stats(&ptr_type, size, true);
71
72        allocation_id
73    }
74
75    pub fn track_deallocation(&mut self, ptr_addr: usize) -> Option<PointerInfo> {
76        if let Some(info) = self.active_pointers.remove(&ptr_addr) {
77            self.update_type_stats(&info.ptr_type, info.allocation_size, false);
78            Some(info)
79        } else {
80            None
81        }
82    }
83
84    pub fn update_ref_count(&mut self, ptr_addr: usize, new_count: usize) -> bool {
85        if let Some(info) = self.active_pointers.get_mut(&ptr_addr) {
86            info.ref_count = Some(new_count);
87            true
88        } else {
89            false
90        }
91    }
92
93    pub fn get_active_count(&self) -> usize {
94        self.active_pointers.len()
95    }
96
97    pub fn get_active_by_type(&self, ptr_type: &PointerType) -> Vec<&PointerInfo> {
98        self.active_pointers
99            .values()
100            .filter(|info| &info.ptr_type == ptr_type)
101            .collect()
102    }
103
104    pub fn get_type_stats(&self, ptr_type: &PointerType) -> Option<&TypeStats> {
105        self.type_stats.get(ptr_type)
106    }
107
108    pub fn get_all_type_stats(&self) -> &HashMap<PointerType, TypeStats> {
109        &self.type_stats
110    }
111
112    pub fn get_memory_usage_by_type(&self) -> HashMap<PointerType, usize> {
113        let mut usage = HashMap::new();
114
115        for (ptr_type, stats) in &self.type_stats {
116            usage.insert(ptr_type.clone(), stats.current_size);
117        }
118
119        usage
120    }
121
122    pub fn find_long_lived_pointers(&self, threshold_secs: u64) -> Vec<&PointerInfo> {
123        let threshold = std::time::Duration::from_secs(threshold_secs);
124        let now = Instant::now();
125
126        self.active_pointers
127            .values()
128            .filter(|info| now.duration_since(info.created_at) > threshold)
129            .collect()
130    }
131
132    pub fn clear(&mut self) {
133        self.active_pointers.clear();
134        self.type_stats.clear();
135        self.allocation_counter.store(0, Ordering::Relaxed);
136    }
137
138    fn update_type_stats(&mut self, ptr_type: &PointerType, size: usize, is_allocation: bool) {
139        let stats = self.type_stats.entry(ptr_type.clone()).or_default();
140
141        if is_allocation {
142            stats.total_count += 1;
143            stats.current_count += 1;
144            stats.total_size += size;
145            stats.current_size += size;
146            stats.max_concurrent = stats.max_concurrent.max(stats.current_count);
147        } else {
148            stats.current_count = stats.current_count.saturating_sub(1);
149            stats.current_size = stats.current_size.saturating_sub(size);
150        }
151    }
152}
153
154impl Default for SmartPointerTracker {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160impl PointerInfo {
161    pub fn age(&self) -> std::time::Duration {
162        Instant::now().duration_since(self.created_at)
163    }
164
165    pub fn age_secs(&self) -> f64 {
166        self.age().as_secs_f64()
167    }
168
169    pub fn is_reference_counted(&self) -> bool {
170        matches!(
171            self.ptr_type,
172            PointerType::Rc | PointerType::Arc | PointerType::Weak
173        )
174    }
175
176    pub fn is_synchronized(&self) -> bool {
177        matches!(
178            self.ptr_type,
179            PointerType::Mutex | PointerType::RwLock | PointerType::Arc
180        )
181    }
182}
183
184impl TypeStats {
185    pub fn average_size(&self) -> f64 {
186        if self.total_count > 0 {
187            self.total_size as f64 / self.total_count as f64
188        } else {
189            0.0
190        }
191    }
192
193    pub fn current_average_size(&self) -> f64 {
194        if self.current_count > 0 {
195            self.current_size as f64 / self.current_count as f64
196        } else {
197            0.0
198        }
199    }
200
201    pub fn allocation_rate(&self, duration_secs: f64) -> f64 {
202        if duration_secs > 0.0 {
203            self.total_count as f64 / duration_secs
204        } else {
205            0.0
206        }
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn test_basic_tracking() {
216        let mut tracker = SmartPointerTracker::new();
217
218        let _id =
219            tracker.track_allocation(0x1000, PointerType::Box, 64, "String".to_string(), None);
220
221        assert_eq!(tracker.get_active_count(), 1);
222        // ID is unsigned, always non-negative
223
224        let info = tracker.track_deallocation(0x1000);
225        assert!(info.is_some());
226        assert_eq!(tracker.get_active_count(), 0);
227    }
228
229    #[test]
230    fn test_type_statistics() {
231        let mut tracker = SmartPointerTracker::new();
232
233        tracker.track_allocation(0x1000, PointerType::Arc, 128, "Data".to_string(), Some(1));
234        tracker.track_allocation(
235            0x2000,
236            PointerType::Arc,
237            256,
238            "Vec<u8>".to_string(),
239            Some(1),
240        );
241
242        let stats = tracker
243            .get_type_stats(&PointerType::Arc)
244            .expect("Stats should exist");
245        assert_eq!(stats.current_count, 2);
246        assert_eq!(stats.current_size, 384);
247        assert_eq!(stats.average_size(), 192.0);
248    }
249
250    #[test]
251    fn test_ref_count_updates() {
252        let mut tracker = SmartPointerTracker::new();
253
254        tracker.track_allocation(0x1000, PointerType::Rc, 64, "String".to_string(), Some(1));
255        assert!(tracker.update_ref_count(0x1000, 3));
256
257        let active = tracker.get_active_by_type(&PointerType::Rc);
258        assert_eq!(active.len(), 1);
259        assert_eq!(active[0].ref_count, Some(3));
260    }
261
262    #[test]
263    fn test_long_lived_detection() {
264        let mut tracker = SmartPointerTracker::new();
265
266        tracker.track_allocation(0x1000, PointerType::Box, 64, "String".to_string(), None);
267
268        // Should not find any long-lived pointers immediately
269        let long_lived = tracker.find_long_lived_pointers(1);
270        assert_eq!(long_lived.len(), 0);
271    }
272}