memscope_rs/smart_pointers/
analyzer.rs1use super::{PointerType, SmartPointerTracker};
2use std::collections::HashMap;
3use std::time::Duration;
4
5#[derive(Debug, Clone)]
6pub enum LeakPattern {
7 CircularReference,
8 WeakReferenceHold,
9 LongLivedBox,
10 HighRefCount,
11 SynchronizationOveruse,
12}
13
14#[derive(Debug, Clone)]
15pub struct AnalysisResult {
16 pub total_active_pointers: usize,
17 pub memory_usage_by_type: HashMap<PointerType, usize>,
18 pub detected_patterns: Vec<(LeakPattern, Vec<u64>)>,
19 pub recommendations: Vec<String>,
20 pub health_score: f64,
21}
22
23pub struct SmartPointerAnalyzer {
24 leak_thresholds: LeakThresholds,
25}
26
27#[derive(Debug)]
28struct LeakThresholds {
29 long_lived_secs: u64,
30 high_ref_count: usize,
31 max_sync_objects: usize,
32 circular_ref_timeout: Duration,
33}
34
35impl Default for LeakThresholds {
36 fn default() -> Self {
37 Self {
38 long_lived_secs: 3600, high_ref_count: 10,
40 max_sync_objects: 100,
41 circular_ref_timeout: Duration::from_secs(300), }
43 }
44}
45
46impl SmartPointerAnalyzer {
47 pub fn new() -> Self {
48 Self {
49 leak_thresholds: LeakThresholds::default(),
50 }
51 }
52
53 pub fn with_thresholds(
54 long_lived_secs: u64,
55 high_ref_count: usize,
56 max_sync_objects: usize,
57 ) -> Self {
58 Self {
59 leak_thresholds: LeakThresholds {
60 long_lived_secs,
61 high_ref_count,
62 max_sync_objects,
63 circular_ref_timeout: Duration::from_secs(300),
64 },
65 }
66 }
67
68 pub fn analyze(&self, tracker: &SmartPointerTracker) -> AnalysisResult {
69 let total_active_pointers = tracker.get_active_count();
70 let memory_usage_by_type = tracker.get_memory_usage_by_type();
71 let mut detected_patterns = Vec::new();
72 let mut recommendations = Vec::new();
73
74 let long_lived_boxes = self.detect_long_lived_boxes(tracker);
76 if !long_lived_boxes.is_empty() {
77 detected_patterns.push((LeakPattern::LongLivedBox, long_lived_boxes));
78 recommendations.push(
79 "Consider using Rc/Arc for shared data instead of long-lived Box".to_string(),
80 );
81 }
82
83 let high_ref_counts = self.detect_high_ref_counts(tracker);
85 if !high_ref_counts.is_empty() {
86 detected_patterns.push((LeakPattern::HighRefCount, high_ref_counts));
87 recommendations.push(
88 "Review objects with high reference counts for potential circular references"
89 .to_string(),
90 );
91 }
92
93 let sync_overuse = self.detect_sync_overuse(tracker);
95 if !sync_overuse.is_empty() {
96 detected_patterns.push((LeakPattern::SynchronizationOveruse, sync_overuse));
97 recommendations.push(
98 "Consider reducing synchronization overhead or using lock-free alternatives"
99 .to_string(),
100 );
101 }
102
103 let circular_refs = self.detect_circular_references(tracker);
105 if !circular_refs.is_empty() {
106 detected_patterns.push((LeakPattern::CircularReference, circular_refs));
107 recommendations.push(
108 "Circular reference detected - consider using Weak references to break cycles"
109 .to_string(),
110 );
111 }
112
113 let weak_holds = self.detect_weak_reference_holds(tracker);
115 if !weak_holds.is_empty() {
116 detected_patterns.push((LeakPattern::WeakReferenceHold, weak_holds));
117 recommendations
118 .push("Check if weak references are being held longer than necessary".to_string());
119 }
120
121 let health_score = self.calculate_health_score(&detected_patterns, total_active_pointers);
122
123 AnalysisResult {
124 total_active_pointers,
125 memory_usage_by_type,
126 detected_patterns,
127 recommendations,
128 health_score,
129 }
130 }
131
132 fn detect_long_lived_boxes(&self, tracker: &SmartPointerTracker) -> Vec<u64> {
133 tracker
134 .find_long_lived_pointers(self.leak_thresholds.long_lived_secs)
135 .iter()
136 .filter(|info| info.ptr_type == PointerType::Box)
137 .map(|info| info.allocation_id)
138 .collect()
139 }
140
141 fn detect_high_ref_counts(&self, tracker: &SmartPointerTracker) -> Vec<u64> {
142 let mut high_ref_ids = Vec::new();
143
144 for ptr_type in &[PointerType::Rc, PointerType::Arc] {
145 let pointers = tracker.get_active_by_type(ptr_type);
146 for info in pointers {
147 if let Some(ref_count) = info.ref_count {
148 if ref_count >= self.leak_thresholds.high_ref_count
150 || (ref_count >= 3 && self.is_likely_circular_reference(info, tracker))
151 {
152 high_ref_ids.push(info.allocation_id);
153 }
154 }
155 }
156 }
157
158 high_ref_ids
159 }
160
161 fn detect_sync_overuse(&self, tracker: &SmartPointerTracker) -> Vec<u64> {
162 let sync_types = [PointerType::Mutex, PointerType::RwLock, PointerType::Arc];
163 let mut sync_count = 0;
164 let mut overuse_ids = Vec::new();
165
166 for sync_type in &sync_types {
167 let pointers = tracker.get_active_by_type(sync_type);
168 sync_count += pointers.len();
169
170 if sync_count > self.leak_thresholds.max_sync_objects {
171 overuse_ids.extend(pointers.iter().map(|info| info.allocation_id));
172 }
173 }
174
175 overuse_ids
176 }
177
178 fn detect_weak_reference_holds(&self, tracker: &SmartPointerTracker) -> Vec<u64> {
179 tracker
180 .get_active_by_type(&PointerType::Weak)
181 .iter()
182 .filter(|info| {
183 info.age() > self.leak_thresholds.circular_ref_timeout ||
185 (info.age() > Duration::from_secs(30) && self.is_suspicious_weak_ref(info, tracker))
187 })
188 .map(|info| info.allocation_id)
189 .collect()
190 }
191
192 fn detect_circular_references(&self, tracker: &SmartPointerTracker) -> Vec<u64> {
194 let mut circular_ids = Vec::new();
195
196 let rc_pointers = tracker.get_active_by_type(&PointerType::Rc);
198 let arc_pointers = tracker.get_active_by_type(&PointerType::Arc);
199
200 circular_ids.extend(self.find_circular_patterns(&rc_pointers));
202 circular_ids.extend(self.find_circular_patterns(&arc_pointers));
204
205 circular_ids
206 }
207
208 fn find_circular_patterns(&self, pointers: &[&super::PointerInfo]) -> Vec<u64> {
209 let mut patterns = Vec::new();
210
211 for (i, info_a) in pointers.iter().enumerate() {
212 for info_b in pointers.iter().skip(i + 1) {
213 if let (Some(ref_a), Some(ref_b)) = (info_a.ref_count, info_b.ref_count) {
215 if ref_a >= 2 && ref_b >= 2 {
216 let age_diff = info_a.age().as_secs().abs_diff(info_b.age().as_secs());
217 let size_ratio =
218 info_a.allocation_size as f64 / info_b.allocation_size as f64;
219
220 if age_diff <= 5 && (0.5..=2.0).contains(&size_ratio) && ref_a == ref_b
224 {
225 patterns.push(info_a.allocation_id);
227 patterns.push(info_b.allocation_id);
228 }
229 }
230 }
231 }
232 }
233
234 patterns
235 }
236
237 fn is_likely_circular_reference(
238 &self,
239 info: &super::PointerInfo,
240 tracker: &SmartPointerTracker,
241 ) -> bool {
242 let same_type_pointers = tracker.get_active_by_type(&info.ptr_type);
244
245 for other in same_type_pointers {
246 if other.allocation_id != info.allocation_id {
247 if let (Some(ref_a), Some(ref_b)) = (info.ref_count, other.ref_count) {
248 if ref_a >= 2 && ref_b >= 2 {
250 let age_diff = info.age().as_secs().abs_diff(other.age().as_secs());
251 if age_diff <= 10 {
252 return true;
254 }
255 }
256 }
257 }
258 }
259
260 false
261 }
262
263 fn is_suspicious_weak_ref(
264 &self,
265 info: &super::PointerInfo,
266 _tracker: &SmartPointerTracker,
267 ) -> bool {
268 info.age() > Duration::from_secs(30)
271 }
272
273 fn calculate_health_score(
274 &self,
275 patterns: &[(LeakPattern, Vec<u64>)],
276 total_pointers: usize,
277 ) -> f64 {
278 if total_pointers == 0 {
279 return 1.0;
280 }
281
282 let mut penalty = 0.0;
283
284 for (pattern, ids) in patterns {
285 let severity = match pattern {
286 LeakPattern::CircularReference => 0.3,
287 LeakPattern::WeakReferenceHold => 0.1,
288 LeakPattern::LongLivedBox => 0.15,
289 LeakPattern::HighRefCount => 0.2,
290 LeakPattern::SynchronizationOveruse => 0.1,
291 };
292
293 let ratio = ids.len() as f64 / total_pointers as f64;
294 penalty += severity * ratio;
295 }
296
297 (1.0 - penalty).max(0.0)
298 }
299
300 pub fn generate_report(&self, result: &AnalysisResult) -> String {
301 let mut report = String::new();
302
303 report.push_str("Smart Pointer Analysis Report\n");
304 report.push_str("============================\n\n");
305
306 report.push_str(&format!(
307 "Total Active Pointers: {}\n",
308 result.total_active_pointers
309 ));
310 report.push_str(&format!(
311 "Health Score: {:.2}/1.00\n\n",
312 result.health_score
313 ));
314
315 report.push_str("Memory Usage by Type:\n");
316 for (ptr_type, usage) in &result.memory_usage_by_type {
317 report.push_str(&format!(" {:?}: {} bytes\n", ptr_type, usage));
318 }
319
320 if !result.detected_patterns.is_empty() {
321 report.push_str("\nDetected Patterns:\n");
322 for (pattern, ids) in &result.detected_patterns {
323 report.push_str(&format!(" {:?}: {} instances\n", pattern, ids.len()));
324 }
325 }
326
327 if !result.recommendations.is_empty() {
328 report.push_str("\nRecommendations:\n");
329 for (i, rec) in result.recommendations.iter().enumerate() {
330 report.push_str(&format!(" {}. {}\n", i + 1, rec));
331 }
332 }
333
334 report
335 }
336}
337
338impl Default for SmartPointerAnalyzer {
339 fn default() -> Self {
340 Self::new()
341 }
342}
343
344impl AnalysisResult {
345 pub fn is_healthy(&self) -> bool {
346 self.health_score >= 0.8
347 }
348
349 pub fn has_critical_issues(&self) -> bool {
350 self.detected_patterns.iter().any(|(pattern, ids)| {
351 matches!(pattern, LeakPattern::CircularReference) && !ids.is_empty()
352 })
353 }
354
355 pub fn total_memory_usage(&self) -> usize {
356 self.memory_usage_by_type.values().sum()
357 }
358
359 pub fn pattern_count(&self, pattern: &LeakPattern) -> usize {
360 self.detected_patterns
361 .iter()
362 .find(|(p, _)| std::mem::discriminant(p) == std::mem::discriminant(pattern))
363 .map(|(_, ids)| ids.len())
364 .unwrap_or(0)
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371
372 #[test]
373 fn test_basic_analysis() {
374 let mut tracker = SmartPointerTracker::new();
375 let analyzer = SmartPointerAnalyzer::new();
376
377 tracker.track_allocation(0x1000, PointerType::Box, 64, "String".to_string(), None);
378 tracker.track_allocation(0x2000, PointerType::Arc, 128, "Data".to_string(), Some(1));
379
380 let result = analyzer.analyze(&tracker);
381
382 assert_eq!(result.total_active_pointers, 2);
383 assert!(result.health_score > 0.8);
384 assert!(!result.has_critical_issues());
385 }
386
387 #[test]
388 fn test_high_ref_count_detection() {
389 let mut tracker = SmartPointerTracker::new();
390 let analyzer = SmartPointerAnalyzer::with_thresholds(3600, 2, 100);
391
392 tracker.track_allocation(0x1000, PointerType::Rc, 64, "String".to_string(), Some(5));
393
394 let result = analyzer.analyze(&tracker);
395
396 assert_eq!(result.pattern_count(&LeakPattern::HighRefCount), 1);
397 assert!(result.health_score < 1.0);
398 }
399
400 #[test]
401 fn test_health_score_calculation() {
402 let tracker = SmartPointerTracker::new();
403 let analyzer = SmartPointerAnalyzer::new();
404
405 let result = analyzer.analyze(&tracker);
406
407 assert_eq!(result.health_score, 1.0);
409 assert!(result.is_healthy());
410 }
411
412 #[test]
413 fn test_report_generation() {
414 let mut tracker = SmartPointerTracker::new();
415 let analyzer = SmartPointerAnalyzer::new();
416
417 tracker.track_allocation(0x1000, PointerType::Box, 64, "String".to_string(), None);
418
419 let result = analyzer.analyze(&tracker);
420 let report = analyzer.generate_report(&result);
421
422 assert!(report.contains("Smart Pointer Analysis Report"));
423 assert!(report.contains("Total Active Pointers: 1"));
424 assert!(report.contains("Health Score:"));
425 }
426}