memscope_rs/smart_pointers/
tracker.rs1use 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 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 let long_lived = tracker.find_long_lived_pointers(1);
270 assert_eq!(long_lived.len(), 0);
271 }
272}