memscope_rs/core/
clone_optimizer.rs

1//! Clone optimization system for reducing unnecessary clone() calls
2//!
3//! This module provides tools to analyze and optimize clone() calls throughout
4//! the codebase by replacing them with Arc-based sharing where appropriate.
5
6use serde::{Deserialize, Serialize};
7
8/// Statistics about clone operations in the system
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct CloneStats {
11    /// Total number of clone operations detected
12    pub total_clones: u64,
13    /// Number of clones that could be optimized with Arc
14    pub optimizable_clones: u64,
15    /// Number of clones already optimized
16    pub optimized_clones: u64,
17    /// Estimated memory saved by optimization (bytes)
18    pub memory_saved_bytes: u64,
19    /// Performance improvement ratio (0.0 to 1.0)
20    pub performance_improvement: f64,
21}
22
23/// Information about a specific clone operation
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct CloneInfo {
26    /// Location where clone occurs (file:line)
27    pub location: String,
28    /// Type being cloned
29    pub type_name: String,
30    /// Estimated size of the clone
31    pub estimated_size: usize,
32    /// Whether this clone can be optimized
33    pub can_optimize: bool,
34    /// Reason why it can/cannot be optimized
35    pub optimization_reason: String,
36}
37
38/// Clone optimization analyzer
39pub struct CloneOptimizer {
40    /// Statistics about clone operations
41    stats: CloneStats,
42    /// Information about individual clone operations
43    clone_info: Vec<CloneInfo>,
44}
45
46impl CloneOptimizer {
47    /// Create a new clone optimizer
48    pub fn new() -> Self {
49        Self {
50            stats: CloneStats {
51                total_clones: 0,
52                optimizable_clones: 0,
53                optimized_clones: 0,
54                memory_saved_bytes: 0,
55                performance_improvement: 0.0,
56            },
57            clone_info: Vec::new(),
58        }
59    }
60
61    /// Record a clone operation
62    pub fn record_clone(&mut self, location: &str, type_name: &str, size: usize) {
63        self.stats.total_clones += 1;
64
65        let can_optimize = self.can_optimize_type(type_name);
66        if can_optimize {
67            self.stats.optimizable_clones += 1;
68        }
69
70        let optimization_reason = if can_optimize {
71            "Can be replaced with Arc sharing".to_string()
72        } else {
73            self.get_optimization_reason(type_name)
74        };
75
76        self.clone_info.push(CloneInfo {
77            location: location.to_string(),
78            type_name: type_name.to_string(),
79            estimated_size: size,
80            can_optimize,
81            optimization_reason,
82        });
83    }
84
85    /// Check if a type can be optimized with Arc sharing
86    fn can_optimize_type(&self, type_name: &str) -> bool {
87        // Types that benefit from Arc sharing
88        matches!(type_name,
89            "AllocationInfo" |
90            "String" |
91            "Vec<_>" |
92            "HashMap<_,_>" |
93            "BTreeMap<_,_>" |
94            "ExportConfig" |
95            "AnalysisResult" |
96            _ if type_name.contains("Config") ||
97                 type_name.contains("Result") ||
98                 type_name.contains("Info") ||
99                 type_name.contains("Data")
100        )
101    }
102
103    /// Get reason why a type cannot be optimized
104    fn get_optimization_reason(&self, type_name: &str) -> String {
105        if type_name.contains("Mutex") || type_name.contains("RwLock") {
106            "Already uses interior mutability".to_string()
107        } else if type_name.contains("Arc") {
108            "Already uses Arc sharing".to_string()
109        } else if type_name.contains("Rc") {
110            "Uses Rc, consider Arc for thread safety".to_string()
111        } else if self.is_primitive_type(type_name) {
112            "Primitive type, clone is cheap".to_string()
113        } else {
114            "Type analysis needed".to_string()
115        }
116    }
117
118    /// Check if a type is primitive
119    fn is_primitive_type(&self, type_name: &str) -> bool {
120        matches!(
121            type_name,
122            "i8" | "i16"
123                | "i32"
124                | "i64"
125                | "i128"
126                | "isize"
127                | "u8"
128                | "u16"
129                | "u32"
130                | "u64"
131                | "u128"
132                | "usize"
133                | "f32"
134                | "f64"
135                | "bool"
136                | "char"
137        )
138    }
139
140    /// Get current statistics
141    pub fn get_stats(&self) -> &CloneStats {
142        &self.stats
143    }
144
145    /// Get clone information
146    pub fn get_clone_info(&self) -> &[CloneInfo] {
147        &self.clone_info
148    }
149
150    /// Mark a clone as optimized
151    pub fn mark_optimized(&mut self, location: &str) {
152        if let Some(info) = self.clone_info.iter_mut().find(|i| i.location == location) {
153            if info.can_optimize {
154                self.stats.optimized_clones += 1;
155                self.stats.memory_saved_bytes += info.estimated_size as u64;
156                info.optimization_reason = "Optimized with Arc sharing".to_string();
157            }
158        }
159
160        // Update performance improvement
161        if self.stats.optimizable_clones > 0 {
162            self.stats.performance_improvement =
163                self.stats.optimized_clones as f64 / self.stats.optimizable_clones as f64;
164        }
165    }
166}
167
168impl Default for CloneOptimizer {
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174/// Check if a type should use Arc sharing based on common patterns
175pub fn should_use_arc(type_name: &str) -> bool {
176    type_name.contains("AllocationInfo")
177        || type_name.contains("Config")
178        || type_name.contains("Result")
179        || type_name.contains("Collection")
180        || type_name.len() > 50 // Large type names often indicate complex types
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_clone_optimizer_new() {
189        let optimizer = CloneOptimizer::new();
190        let stats = optimizer.get_stats();
191
192        assert_eq!(stats.total_clones, 0);
193        assert_eq!(stats.optimizable_clones, 0);
194        assert_eq!(stats.optimized_clones, 0);
195        assert_eq!(stats.memory_saved_bytes, 0);
196        assert_eq!(stats.performance_improvement, 0.0);
197        assert!(optimizer.get_clone_info().is_empty());
198    }
199
200    #[test]
201    fn test_clone_optimizer_default() {
202        let optimizer = CloneOptimizer::default();
203        let stats = optimizer.get_stats();
204
205        assert_eq!(stats.total_clones, 0);
206        assert_eq!(stats.optimizable_clones, 0);
207        assert_eq!(stats.optimized_clones, 0);
208        assert_eq!(stats.memory_saved_bytes, 0);
209        assert_eq!(stats.performance_improvement, 0.0);
210    }
211
212    #[test]
213    fn test_record_optimizable_clone() {
214        let mut optimizer = CloneOptimizer::new();
215
216        optimizer.record_clone("main.rs:10", "AllocationInfo", 256);
217
218        let stats = optimizer.get_stats();
219        assert_eq!(stats.total_clones, 1);
220        assert_eq!(stats.optimizable_clones, 1);
221        assert_eq!(stats.optimized_clones, 0);
222
223        let clone_info = optimizer.get_clone_info();
224        assert_eq!(clone_info.len(), 1);
225        assert_eq!(clone_info[0].location, "main.rs:10");
226        assert_eq!(clone_info[0].type_name, "AllocationInfo");
227        assert_eq!(clone_info[0].estimated_size, 256);
228        assert!(clone_info[0].can_optimize);
229        assert_eq!(
230            clone_info[0].optimization_reason,
231            "Can be replaced with Arc sharing"
232        );
233    }
234
235    #[test]
236    fn test_record_non_optimizable_clone() {
237        let mut optimizer = CloneOptimizer::new();
238
239        optimizer.record_clone("main.rs:20", "i32", 4);
240
241        let stats = optimizer.get_stats();
242        assert_eq!(stats.total_clones, 1);
243        assert_eq!(stats.optimizable_clones, 0);
244        assert_eq!(stats.optimized_clones, 0);
245
246        let clone_info = optimizer.get_clone_info();
247        assert_eq!(clone_info.len(), 1);
248        assert_eq!(clone_info[0].location, "main.rs:20");
249        assert_eq!(clone_info[0].type_name, "i32");
250        assert_eq!(clone_info[0].estimated_size, 4);
251        assert!(!clone_info[0].can_optimize);
252        assert_eq!(
253            clone_info[0].optimization_reason,
254            "Primitive type, clone is cheap"
255        );
256    }
257
258    #[test]
259    fn test_can_optimize_type_optimizable_types() {
260        let optimizer = CloneOptimizer::new();
261
262        // Test exact matches that definitely work
263        assert!(optimizer.can_optimize_type("AllocationInfo"));
264        assert!(optimizer.can_optimize_type("ExportConfig"));
265        assert!(optimizer.can_optimize_type("AnalysisResult"));
266
267        // Test pattern matches
268        assert!(optimizer.can_optimize_type("MyConfig")); // contains "Config"
269        assert!(optimizer.can_optimize_type("TestResult")); // contains "Result"
270        assert!(optimizer.can_optimize_type("UserInfo")); // contains "Info"
271        assert!(optimizer.can_optimize_type("ProcessData")); // contains "Data"
272    }
273
274    #[test]
275    fn test_can_optimize_type_non_optimizable_types() {
276        let optimizer = CloneOptimizer::new();
277
278        assert!(!optimizer.can_optimize_type("i32"));
279        assert!(!optimizer.can_optimize_type("u64"));
280        assert!(!optimizer.can_optimize_type("f64"));
281        assert!(!optimizer.can_optimize_type("bool"));
282        assert!(!optimizer.can_optimize_type("char"));
283        assert!(!optimizer.can_optimize_type("SomeOtherType"));
284    }
285
286    #[test]
287    fn test_is_primitive_type() {
288        let optimizer = CloneOptimizer::new();
289
290        // Test all integer types
291        assert!(optimizer.is_primitive_type("i8"));
292        assert!(optimizer.is_primitive_type("i16"));
293        assert!(optimizer.is_primitive_type("i32"));
294        assert!(optimizer.is_primitive_type("i64"));
295        assert!(optimizer.is_primitive_type("i128"));
296        assert!(optimizer.is_primitive_type("isize"));
297        assert!(optimizer.is_primitive_type("u8"));
298        assert!(optimizer.is_primitive_type("u16"));
299        assert!(optimizer.is_primitive_type("u32"));
300        assert!(optimizer.is_primitive_type("u64"));
301        assert!(optimizer.is_primitive_type("u128"));
302        assert!(optimizer.is_primitive_type("usize"));
303
304        // Test floating point types
305        assert!(optimizer.is_primitive_type("f32"));
306        assert!(optimizer.is_primitive_type("f64"));
307
308        // Test other primitive types
309        assert!(optimizer.is_primitive_type("bool"));
310        assert!(optimizer.is_primitive_type("char"));
311
312        // Test non-primitive types
313        assert!(!optimizer.is_primitive_type("String"));
314        assert!(!optimizer.is_primitive_type("Vec<i32>"));
315        assert!(!optimizer.is_primitive_type("AllocationInfo"));
316    }
317
318    #[test]
319    fn test_get_optimization_reason_mutex_types() {
320        let optimizer = CloneOptimizer::new();
321
322        let reason = optimizer.get_optimization_reason("Mutex<i32>");
323        assert_eq!(reason, "Already uses interior mutability");
324
325        let reason = optimizer.get_optimization_reason("RwLock<String>");
326        assert_eq!(reason, "Already uses interior mutability");
327    }
328
329    #[test]
330    fn test_get_optimization_reason_arc_types() {
331        let optimizer = CloneOptimizer::new();
332
333        let reason = optimizer.get_optimization_reason("Arc<String>");
334        assert_eq!(reason, "Already uses Arc sharing");
335    }
336
337    #[test]
338    fn test_get_optimization_reason_rc_types() {
339        let optimizer = CloneOptimizer::new();
340
341        let reason = optimizer.get_optimization_reason("Rc<String>");
342        assert_eq!(reason, "Uses Rc, consider Arc for thread safety");
343    }
344
345    #[test]
346    fn test_get_optimization_reason_primitive_types() {
347        let optimizer = CloneOptimizer::new();
348
349        let reason = optimizer.get_optimization_reason("i32");
350        assert_eq!(reason, "Primitive type, clone is cheap");
351
352        let reason = optimizer.get_optimization_reason("bool");
353        assert_eq!(reason, "Primitive type, clone is cheap");
354    }
355
356    #[test]
357    fn test_get_optimization_reason_unknown_types() {
358        let optimizer = CloneOptimizer::new();
359
360        let reason = optimizer.get_optimization_reason("UnknownType");
361        assert_eq!(reason, "Type analysis needed");
362    }
363
364    #[test]
365    fn test_mark_optimized_valid_location() {
366        let mut optimizer = CloneOptimizer::new();
367
368        // Record an optimizable clone
369        optimizer.record_clone("main.rs:10", "AllocationInfo", 256);
370
371        // Mark it as optimized
372        optimizer.mark_optimized("main.rs:10");
373
374        let stats = optimizer.get_stats();
375        assert_eq!(stats.optimized_clones, 1);
376        assert_eq!(stats.memory_saved_bytes, 256);
377        assert_eq!(stats.performance_improvement, 1.0); // 1/1 = 100%
378
379        let clone_info = optimizer.get_clone_info();
380        assert_eq!(
381            clone_info[0].optimization_reason,
382            "Optimized with Arc sharing"
383        );
384    }
385
386    #[test]
387    fn test_mark_optimized_invalid_location() {
388        let mut optimizer = CloneOptimizer::new();
389
390        // Record an optimizable clone
391        optimizer.record_clone("main.rs:10", "AllocationInfo", 256);
392
393        // Try to mark a different location as optimized
394        optimizer.mark_optimized("main.rs:20");
395
396        let stats = optimizer.get_stats();
397        assert_eq!(stats.optimized_clones, 0);
398        assert_eq!(stats.memory_saved_bytes, 0);
399        assert_eq!(stats.performance_improvement, 0.0);
400    }
401
402    #[test]
403    fn test_mark_optimized_non_optimizable_clone() {
404        let mut optimizer = CloneOptimizer::new();
405
406        // Record a non-optimizable clone
407        optimizer.record_clone("main.rs:10", "i32", 4);
408
409        // Try to mark it as optimized
410        optimizer.mark_optimized("main.rs:10");
411
412        let stats = optimizer.get_stats();
413        assert_eq!(stats.optimized_clones, 0);
414        assert_eq!(stats.memory_saved_bytes, 0);
415        assert_eq!(stats.performance_improvement, 0.0);
416    }
417
418    #[test]
419    fn test_performance_improvement_calculation() {
420        let mut optimizer = CloneOptimizer::new();
421
422        // Record multiple optimizable clones
423        optimizer.record_clone("main.rs:10", "AllocationInfo", 256);
424        optimizer.record_clone("main.rs:20", "ExportConfig", 128);
425        optimizer.record_clone("main.rs:30", "Vec<_>", 512);
426        optimizer.record_clone("main.rs:40", "i32", 4); // Non-optimizable
427
428        let stats = optimizer.get_stats();
429        assert_eq!(stats.total_clones, 4);
430        assert_eq!(stats.optimizable_clones, 2); // Only AllocationInfo and ExportConfig are optimizable
431        assert_eq!(stats.optimized_clones, 0);
432
433        // Optimize 2 out of 2 optimizable clones
434        optimizer.mark_optimized("main.rs:10");
435        optimizer.mark_optimized("main.rs:20");
436
437        let stats = optimizer.get_stats();
438        assert_eq!(stats.optimized_clones, 2);
439        assert_eq!(stats.memory_saved_bytes, 384); // 256 + 128
440        assert!((stats.performance_improvement - 1.0).abs() < f64::EPSILON); // 2/2 = 100%
441    }
442
443    #[test]
444    fn test_multiple_clone_records() {
445        let mut optimizer = CloneOptimizer::new();
446
447        // Record various types of clones
448        optimizer.record_clone("file1.rs:10", "AllocationInfo", 256);
449        optimizer.record_clone("file2.rs:20", "i32", 4);
450        optimizer.record_clone("file3.rs:30", "ExportConfig", 64);
451        optimizer.record_clone("file4.rs:40", "Mutex<i32>", 32);
452        optimizer.record_clone("file5.rs:50", "Arc<String>", 48);
453
454        let stats = optimizer.get_stats();
455        assert_eq!(stats.total_clones, 5);
456        assert_eq!(stats.optimizable_clones, 2); // AllocationInfo and ExportConfig
457
458        let clone_info = optimizer.get_clone_info();
459        assert_eq!(clone_info.len(), 5);
460
461        // Check specific clone info
462        assert!(clone_info[0].can_optimize); // AllocationInfo
463        assert!(!clone_info[1].can_optimize); // i32
464        assert!(clone_info[2].can_optimize); // ExportConfig
465        assert!(!clone_info[3].can_optimize); // Mutex<i32>
466        assert!(!clone_info[4].can_optimize); // Arc<String>
467    }
468
469    #[test]
470    fn test_should_use_arc_function() {
471        assert!(should_use_arc("AllocationInfo"));
472        assert!(should_use_arc("MyConfig"));
473        assert!(should_use_arc("TestResult"));
474        assert!(should_use_arc("DataCollection"));
475        assert!(should_use_arc(
476            "VeryLongTypeNameThatExceedsFiftyCharactersInLengthAndMoreToMakeSureItIsLongEnough"
477        ));
478
479        assert!(!should_use_arc("i32"));
480        assert!(!should_use_arc("String"));
481        assert!(!should_use_arc("ShortType"));
482    }
483
484    #[test]
485    fn test_clone_stats_serialization() {
486        let stats = CloneStats {
487            total_clones: 10,
488            optimizable_clones: 7,
489            optimized_clones: 5,
490            memory_saved_bytes: 1024,
491            performance_improvement: 0.714,
492        };
493
494        // Test that it can be serialized and deserialized
495        let serialized = serde_json::to_string(&stats).expect("Failed to serialize");
496        let deserialized: CloneStats =
497            serde_json::from_str(&serialized).expect("Failed to deserialize");
498
499        assert_eq!(deserialized.total_clones, 10);
500        assert_eq!(deserialized.optimizable_clones, 7);
501        assert_eq!(deserialized.optimized_clones, 5);
502        assert_eq!(deserialized.memory_saved_bytes, 1024);
503        assert!((deserialized.performance_improvement - 0.714).abs() < f64::EPSILON);
504    }
505
506    #[test]
507    fn test_clone_info_serialization() {
508        let info = CloneInfo {
509            location: "main.rs:42".to_string(),
510            type_name: "AllocationInfo".to_string(),
511            estimated_size: 256,
512            can_optimize: true,
513            optimization_reason: "Can be replaced with Arc sharing".to_string(),
514        };
515
516        // Test that it can be serialized and deserialized
517        let serialized = serde_json::to_string(&info).expect("Failed to serialize");
518        let deserialized: CloneInfo =
519            serde_json::from_str(&serialized).expect("Failed to deserialize");
520
521        assert_eq!(deserialized.location, "main.rs:42");
522        assert_eq!(deserialized.type_name, "AllocationInfo");
523        assert_eq!(deserialized.estimated_size, 256);
524        assert!(deserialized.can_optimize);
525        assert_eq!(
526            deserialized.optimization_reason,
527            "Can be replaced with Arc sharing"
528        );
529    }
530
531    #[test]
532    fn test_clone_stats_debug() {
533        let stats = CloneStats {
534            total_clones: 5,
535            optimizable_clones: 3,
536            optimized_clones: 2,
537            memory_saved_bytes: 512,
538            performance_improvement: 0.667,
539        };
540
541        let debug_str = format!("{stats:?}");
542        assert!(debug_str.contains("total_clones: 5"));
543        assert!(debug_str.contains("optimizable_clones: 3"));
544        assert!(debug_str.contains("optimized_clones: 2"));
545    }
546
547    #[test]
548    fn test_clone_info_debug() {
549        let info = CloneInfo {
550            location: "test.rs:1".to_string(),
551            type_name: "TestType".to_string(),
552            estimated_size: 128,
553            can_optimize: false,
554            optimization_reason: "Test reason".to_string(),
555        };
556
557        let debug_str = format!("{info:?}");
558        assert!(debug_str.contains("location: \"test.rs:1\""));
559        assert!(debug_str.contains("type_name: \"TestType\""));
560        assert!(debug_str.contains("estimated_size: 128"));
561    }
562
563    #[test]
564    fn test_edge_case_empty_type_name() {
565        let mut optimizer = CloneOptimizer::new();
566
567        optimizer.record_clone("main.rs:1", "", 0);
568
569        let clone_info = optimizer.get_clone_info();
570        assert_eq!(clone_info.len(), 1);
571        assert_eq!(clone_info[0].type_name, "");
572        assert!(!clone_info[0].can_optimize);
573        assert_eq!(clone_info[0].optimization_reason, "Type analysis needed");
574    }
575
576    #[test]
577    fn test_edge_case_zero_size_clone() {
578        let mut optimizer = CloneOptimizer::new();
579
580        optimizer.record_clone("main.rs:1", "AllocationInfo", 0);
581        optimizer.mark_optimized("main.rs:1");
582
583        let stats = optimizer.get_stats();
584        assert_eq!(stats.memory_saved_bytes, 0);
585        assert_eq!(stats.optimized_clones, 1);
586    }
587}