memscope_rs/lockfree/
analysis.rs

1//! Analysis structures and types for lock-free tracking results
2//!
3//! This module defines the data structures used to represent analysis
4//! results from lock-free multi-threaded tracking data.
5
6use bincode::{Decode, Encode};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// Per-thread statistics from lock-free tracking
11#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
12pub struct ThreadStats {
13    /// Thread identifier
14    pub thread_id: u64,
15    /// Total number of allocations tracked
16    pub total_allocations: u64,
17    /// Total number of deallocations tracked
18    pub total_deallocations: u64,
19    /// Peak memory usage observed
20    pub peak_memory: usize,
21    /// Total bytes allocated (sum of all allocations)
22    pub total_allocated: usize,
23    /// Frequency of allocations per call stack
24    pub allocation_frequency: HashMap<u64, u64>,
25    /// Average allocation size for this thread
26    pub avg_allocation_size: f64,
27    /// Timeline of allocation events (sampled)
28    pub timeline: Vec<AllocationEvent>,
29}
30
31/// Allocation event from lock-free tracking
32#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
33pub struct AllocationEvent {
34    /// Timestamp in nanoseconds
35    pub timestamp: u64,
36    /// Memory pointer address
37    pub ptr: usize,
38    /// Allocation size in bytes
39    pub size: usize,
40    /// Hash of the call stack
41    pub call_stack_hash: u64,
42    /// Event type (allocation or deallocation)
43    pub event_type: EventType,
44    /// Thread that performed the operation
45    pub thread_id: u64,
46}
47
48/// Type of memory event
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Encode, Decode)]
50pub enum EventType {
51    /// Memory allocation event
52    Allocation,
53    /// Memory deallocation event
54    Deallocation,
55}
56
57/// Comprehensive analysis results from multiple threads
58#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
59pub struct LockfreeAnalysis {
60    /// Statistics for each thread
61    pub thread_stats: HashMap<u64, ThreadStats>,
62    /// Most frequently used call stacks across all threads
63    pub hottest_call_stacks: Vec<HotCallStack>,
64    /// Detected interactions between threads
65    pub thread_interactions: Vec<ThreadInteraction>,
66    /// Memory usage peaks across all threads
67    pub memory_peaks: Vec<MemoryPeak>,
68    /// Detected performance bottlenecks
69    pub performance_bottlenecks: Vec<PerformanceBottleneck>,
70    /// Overall summary statistics
71    pub summary: AnalysisSummary,
72}
73
74/// Hot call stack information
75#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
76pub struct HotCallStack {
77    /// Hash of the call stack
78    pub call_stack_hash: u64,
79    /// Total frequency across all threads
80    pub total_frequency: u64,
81    /// Total memory allocated by this call stack
82    pub total_size: usize,
83    /// Impact score (frequency * size)
84    pub impact_score: u64,
85    /// Threads that use this call stack
86    pub threads: Vec<u64>,
87}
88
89/// Interaction between threads
90#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
91pub struct ThreadInteraction {
92    /// First thread in the interaction
93    pub thread_a: u64,
94    /// Second thread in the interaction
95    pub thread_b: u64,
96    /// Shared memory regions (simplified as call stack hashes)
97    pub shared_patterns: Vec<u64>,
98    /// Frequency of interaction
99    pub interaction_strength: u64,
100    /// Type of interaction detected
101    pub interaction_type: InteractionType,
102}
103
104/// Type of thread interaction
105#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
106pub enum InteractionType {
107    /// Similar allocation patterns
108    SimilarPatterns,
109    /// Potential memory sharing
110    MemorySharing,
111    /// Producer-consumer relationship
112    ProducerConsumer,
113}
114
115/// Memory usage peak
116#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
117pub struct MemoryPeak {
118    /// Timestamp of the peak
119    pub timestamp: u64,
120    /// Thread that caused the peak
121    pub thread_id: u64,
122    /// Memory usage at peak (bytes)
123    pub memory_usage: usize,
124    /// Number of active allocations
125    pub active_allocations: u64,
126    /// Call stack that triggered the peak
127    pub triggering_call_stack: u64,
128}
129
130/// Performance bottleneck detection
131#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
132pub struct PerformanceBottleneck {
133    /// Type of bottleneck detected
134    pub bottleneck_type: BottleneckType,
135    /// Thread where bottleneck was detected
136    pub thread_id: u64,
137    /// Call stack associated with bottleneck
138    pub call_stack_hash: u64,
139    /// Severity score (0.0 to 1.0)
140    pub severity: f64,
141    /// Human-readable description
142    pub description: String,
143    /// Suggested remediation
144    pub suggestion: String,
145}
146
147/// Type of performance bottleneck
148#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
149pub enum BottleneckType {
150    /// High frequency small allocations
151    HighFrequencySmallAllocation,
152    /// Large allocation spike
153    LargeAllocationSpike,
154    /// Potential memory leak
155    MemoryLeak,
156    /// Thread contention (in case any remains)
157    ThreadContention,
158    /// Fragmentation risk
159    FragmentationRisk,
160}
161
162/// Overall analysis summary
163#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
164pub struct AnalysisSummary {
165    /// Total number of threads analyzed
166    pub total_threads: usize,
167    /// Total allocations across all threads
168    pub total_allocations: u64,
169    /// Total deallocations across all threads
170    pub total_deallocations: u64,
171    /// Peak memory usage across all threads
172    pub peak_memory_usage: usize,
173    /// Total memory allocated across all threads
174    pub total_memory_allocated: usize,
175    /// Number of unique call stacks detected
176    pub unique_call_stacks: usize,
177    /// Analysis duration
178    pub analysis_duration_ms: u64,
179    /// Sampling effectiveness (percentage of allocations sampled)
180    pub sampling_effectiveness: f64,
181}
182
183impl LockfreeAnalysis {
184    /// Creates an empty analysis result
185    pub fn new() -> Self {
186        Self {
187            thread_stats: HashMap::new(),
188            hottest_call_stacks: Vec::new(),
189            thread_interactions: Vec::new(),
190            memory_peaks: Vec::new(),
191            performance_bottlenecks: Vec::new(),
192            summary: AnalysisSummary {
193                total_threads: 0,
194                total_allocations: 0,
195                total_deallocations: 0,
196                peak_memory_usage: 0,
197                total_memory_allocated: 0,
198                unique_call_stacks: 0,
199                analysis_duration_ms: 0,
200                sampling_effectiveness: 0.0,
201            },
202        }
203    }
204
205    /// Calculates and updates the summary statistics
206    pub fn calculate_summary(&mut self, analysis_start: std::time::Instant) {
207        let mut total_allocations = 0;
208        let mut total_deallocations = 0;
209        let mut peak_memory = 0;
210        let mut total_allocated = 0;
211        let mut all_call_stacks = std::collections::HashSet::new();
212
213        for stats in self.thread_stats.values() {
214            total_allocations += stats.total_allocations;
215            total_deallocations += stats.total_deallocations;
216            peak_memory += stats.peak_memory; // Fix: accumulate peak memory from all threads
217            total_allocated += stats.total_allocated;
218
219            for &call_stack_hash in stats.allocation_frequency.keys() {
220                all_call_stacks.insert(call_stack_hash);
221            }
222        }
223
224        self.summary = AnalysisSummary {
225            total_threads: self.thread_stats.len(),
226            total_allocations,
227            total_deallocations,
228            peak_memory_usage: peak_memory,
229            total_memory_allocated: total_allocated,
230            unique_call_stacks: all_call_stacks.len(),
231            analysis_duration_ms: analysis_start.elapsed().as_millis() as u64,
232            sampling_effectiveness: if total_allocations > 0 {
233                // Estimate based on sampled data vs expected full data
234                100.0 // Placeholder - would need more sophisticated calculation
235            } else {
236                0.0
237            },
238        };
239    }
240
241    /// Gets threads with highest allocation activity
242    pub fn get_most_active_threads(&self, limit: usize) -> Vec<(u64, u64)> {
243        let mut thread_activity: Vec<_> = self
244            .thread_stats
245            .iter()
246            .map(|(&thread_id, stats)| (thread_id, stats.total_allocations))
247            .collect();
248
249        thread_activity.sort_by(|a, b| b.1.cmp(&a.1));
250        thread_activity.truncate(limit);
251        thread_activity
252    }
253
254    /// Gets threads with highest memory usage
255    pub fn get_highest_memory_threads(&self, limit: usize) -> Vec<(u64, usize)> {
256        let mut thread_memory: Vec<_> = self
257            .thread_stats
258            .iter()
259            .map(|(&thread_id, stats)| (thread_id, stats.peak_memory))
260            .collect();
261
262        thread_memory.sort_by(|a, b| b.1.cmp(&a.1));
263        thread_memory.truncate(limit);
264        thread_memory
265    }
266
267    /// Gets most severe performance bottlenecks
268    pub fn get_critical_bottlenecks(&self, severity_threshold: f64) -> Vec<&PerformanceBottleneck> {
269        self.performance_bottlenecks
270            .iter()
271            .filter(|b| b.severity >= severity_threshold)
272            .collect()
273    }
274}
275
276impl Default for LockfreeAnalysis {
277    fn default() -> Self {
278        Self::new()
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285    use std::time::Instant;
286
287    fn create_test_thread_stats(
288        thread_id: u64,
289        allocations: u64,
290        peak_memory: usize,
291    ) -> ThreadStats {
292        let mut allocation_frequency = HashMap::new();
293        allocation_frequency.insert(12345, allocations / 2);
294        allocation_frequency.insert(67890, allocations / 2);
295
296        ThreadStats {
297            thread_id,
298            total_allocations: allocations,
299            total_deallocations: allocations - 1, // Simulate one outstanding allocation
300            peak_memory,
301            total_allocated: peak_memory,
302            allocation_frequency,
303            avg_allocation_size: peak_memory as f64 / allocations as f64,
304            timeline: vec![
305                AllocationEvent {
306                    timestamp: 1000,
307                    ptr: 0x1000,
308                    size: 1024,
309                    call_stack_hash: 12345,
310                    event_type: EventType::Allocation,
311                    thread_id,
312                },
313                AllocationEvent {
314                    timestamp: 2000,
315                    ptr: 0x2000,
316                    size: 2048,
317                    call_stack_hash: 67890,
318                    event_type: EventType::Allocation,
319                    thread_id,
320                },
321            ],
322        }
323    }
324
325    #[test]
326    fn test_lockfree_analysis_creation() {
327        let analysis = LockfreeAnalysis::new();
328
329        assert!(analysis.thread_stats.is_empty());
330        assert!(analysis.hottest_call_stacks.is_empty());
331        assert!(analysis.thread_interactions.is_empty());
332        assert!(analysis.memory_peaks.is_empty());
333        assert!(analysis.performance_bottlenecks.is_empty());
334        assert_eq!(analysis.summary.total_threads, 0);
335        assert_eq!(analysis.summary.total_allocations, 0);
336    }
337
338    #[test]
339    fn test_lockfree_analysis_default() {
340        let analysis = LockfreeAnalysis::default();
341        assert!(analysis.thread_stats.is_empty());
342        assert_eq!(analysis.summary.total_threads, 0);
343    }
344
345    #[test]
346    fn test_calculate_summary_single_thread() {
347        let mut analysis = LockfreeAnalysis::new();
348        let start_time = Instant::now();
349
350        // Add a single thread
351        let thread_stats = create_test_thread_stats(1, 100, 8192);
352        analysis.thread_stats.insert(1, thread_stats);
353
354        analysis.calculate_summary(start_time);
355
356        assert_eq!(analysis.summary.total_threads, 1);
357        assert_eq!(analysis.summary.total_allocations, 100);
358        assert_eq!(analysis.summary.total_deallocations, 99);
359        assert_eq!(analysis.summary.peak_memory_usage, 8192); // Single thread
360        assert_eq!(analysis.summary.total_memory_allocated, 8192);
361        assert_eq!(analysis.summary.unique_call_stacks, 2); // Two call stacks in test data
362                                                            // assert!(analysis.summary.analysis_duration_ms >= 0); // Always true for u64
363    }
364
365    #[test]
366    fn test_calculate_summary_multiple_threads() {
367        let mut analysis = LockfreeAnalysis::new();
368        let start_time = Instant::now();
369
370        // Add multiple threads
371        analysis
372            .thread_stats
373            .insert(1, create_test_thread_stats(1, 100, 4096));
374        analysis
375            .thread_stats
376            .insert(2, create_test_thread_stats(2, 50, 2048));
377        analysis
378            .thread_stats
379            .insert(3, create_test_thread_stats(3, 200, 8192));
380
381        analysis.calculate_summary(start_time);
382
383        assert_eq!(analysis.summary.total_threads, 3);
384        assert_eq!(analysis.summary.total_allocations, 350); // 100 + 50 + 200
385        assert_eq!(analysis.summary.total_deallocations, 347); // 99 + 49 + 199
386        assert_eq!(analysis.summary.peak_memory_usage, 14336); // Sum of all threads: 4096+2048+8192
387        assert_eq!(analysis.summary.total_memory_allocated, 14336); // Sum of all threads
388        assert_eq!(analysis.summary.unique_call_stacks, 2); // Same call stacks used by all
389    }
390
391    #[test]
392    fn test_get_most_active_threads() {
393        let mut analysis = LockfreeAnalysis::new();
394
395        analysis
396            .thread_stats
397            .insert(1, create_test_thread_stats(1, 100, 4096));
398        analysis
399            .thread_stats
400            .insert(2, create_test_thread_stats(2, 300, 2048));
401        analysis
402            .thread_stats
403            .insert(3, create_test_thread_stats(3, 50, 8192));
404
405        let most_active = analysis.get_most_active_threads(2);
406
407        assert_eq!(most_active.len(), 2);
408        assert_eq!(most_active[0], (2, 300)); // Most active thread
409        assert_eq!(most_active[1], (1, 100)); // Second most active
410    }
411
412    #[test]
413    fn test_get_most_active_threads_empty() {
414        let analysis = LockfreeAnalysis::new();
415        let most_active = analysis.get_most_active_threads(5);
416        assert!(most_active.is_empty());
417    }
418
419    #[test]
420    fn test_get_highest_memory_threads() {
421        let mut analysis = LockfreeAnalysis::new();
422
423        analysis
424            .thread_stats
425            .insert(1, create_test_thread_stats(1, 100, 4096));
426        analysis
427            .thread_stats
428            .insert(2, create_test_thread_stats(2, 300, 2048));
429        analysis
430            .thread_stats
431            .insert(3, create_test_thread_stats(3, 50, 8192));
432
433        let highest_memory = analysis.get_highest_memory_threads(2);
434
435        assert_eq!(highest_memory.len(), 2);
436        assert_eq!(highest_memory[0], (3, 8192)); // Highest memory usage
437        assert_eq!(highest_memory[1], (1, 4096)); // Second highest
438    }
439
440    #[test]
441    fn test_get_critical_bottlenecks() {
442        let mut analysis = LockfreeAnalysis::new();
443
444        analysis
445            .performance_bottlenecks
446            .push(PerformanceBottleneck {
447                bottleneck_type: BottleneckType::HighFrequencySmallAllocation,
448                thread_id: 1,
449                call_stack_hash: 12345,
450                severity: 0.8,
451                description: "High frequency allocations detected".to_string(),
452                suggestion: "Consider using a memory pool".to_string(),
453            });
454
455        analysis
456            .performance_bottlenecks
457            .push(PerformanceBottleneck {
458                bottleneck_type: BottleneckType::MemoryLeak,
459                thread_id: 2,
460                call_stack_hash: 67890,
461                severity: 0.3,
462                description: "Potential memory leak".to_string(),
463                suggestion: "Check deallocation patterns".to_string(),
464            });
465
466        let critical = analysis.get_critical_bottlenecks(0.7);
467        assert_eq!(critical.len(), 1);
468        assert_eq!(critical[0].severity, 0.8);
469
470        let all_bottlenecks = analysis.get_critical_bottlenecks(0.0);
471        assert_eq!(all_bottlenecks.len(), 2);
472    }
473
474    #[test]
475    fn test_event_type_equality() {
476        assert_eq!(EventType::Allocation, EventType::Allocation);
477        assert_eq!(EventType::Deallocation, EventType::Deallocation);
478        assert_ne!(EventType::Allocation, EventType::Deallocation);
479    }
480
481    #[test]
482    fn test_allocation_event_creation() {
483        let event = AllocationEvent {
484            timestamp: 12345,
485            ptr: 0x1000,
486            size: 1024,
487            call_stack_hash: 67890,
488            event_type: EventType::Allocation,
489            thread_id: 1,
490        };
491
492        assert_eq!(event.timestamp, 12345);
493        assert_eq!(event.ptr, 0x1000);
494        assert_eq!(event.size, 1024);
495        assert_eq!(event.call_stack_hash, 67890);
496        assert_eq!(event.event_type, EventType::Allocation);
497        assert_eq!(event.thread_id, 1);
498    }
499
500    #[test]
501    fn test_thread_stats_creation() {
502        let stats = create_test_thread_stats(42, 1000, 16384);
503
504        assert_eq!(stats.thread_id, 42);
505        assert_eq!(stats.total_allocations, 1000);
506        assert_eq!(stats.total_deallocations, 999);
507        assert_eq!(stats.peak_memory, 16384);
508        assert_eq!(stats.total_allocated, 16384);
509        assert_eq!(stats.avg_allocation_size, 16.384);
510        assert_eq!(stats.allocation_frequency.len(), 2);
511        assert_eq!(stats.timeline.len(), 2);
512    }
513
514    #[test]
515    fn test_hot_call_stack() {
516        let hot_stack = HotCallStack {
517            call_stack_hash: 12345,
518            total_frequency: 100,
519            total_size: 8192,
520            impact_score: 819200,
521            threads: vec![1, 2, 3],
522        };
523
524        assert_eq!(hot_stack.call_stack_hash, 12345);
525        assert_eq!(hot_stack.total_frequency, 100);
526        assert_eq!(hot_stack.total_size, 8192);
527        assert_eq!(hot_stack.impact_score, 819200);
528        assert_eq!(hot_stack.threads.len(), 3);
529    }
530
531    #[test]
532    fn test_thread_interaction() {
533        let interaction = ThreadInteraction {
534            thread_a: 1,
535            thread_b: 2,
536            shared_patterns: vec![12345, 67890],
537            interaction_strength: 50,
538            interaction_type: InteractionType::SimilarPatterns,
539        };
540
541        assert_eq!(interaction.thread_a, 1);
542        assert_eq!(interaction.thread_b, 2);
543        assert_eq!(interaction.shared_patterns.len(), 2);
544        assert_eq!(interaction.interaction_strength, 50);
545    }
546
547    #[test]
548    fn test_memory_peak() {
549        let peak = MemoryPeak {
550            timestamp: 123456789,
551            thread_id: 3,
552            memory_usage: 1048576,
553            active_allocations: 500,
554            triggering_call_stack: 12345,
555        };
556
557        assert_eq!(peak.timestamp, 123456789);
558        assert_eq!(peak.thread_id, 3);
559        assert_eq!(peak.memory_usage, 1048576);
560        assert_eq!(peak.active_allocations, 500);
561        assert_eq!(peak.triggering_call_stack, 12345);
562    }
563
564    #[test]
565    fn test_performance_bottleneck_types() {
566        let bottleneck1 = PerformanceBottleneck {
567            bottleneck_type: BottleneckType::HighFrequencySmallAllocation,
568            thread_id: 1,
569            call_stack_hash: 12345,
570            severity: 0.8,
571            description: "High frequency".to_string(),
572            suggestion: "Use pools".to_string(),
573        };
574
575        let bottleneck2 = PerformanceBottleneck {
576            bottleneck_type: BottleneckType::LargeAllocationSpike,
577            thread_id: 2,
578            call_stack_hash: 67890,
579            severity: 0.6,
580            description: "Large spike".to_string(),
581            suggestion: "Optimize allocation".to_string(),
582        };
583
584        // Test different bottleneck types can be created
585        assert_eq!(bottleneck1.thread_id, 1);
586        assert_eq!(bottleneck2.thread_id, 2);
587    }
588
589    #[test]
590    fn test_interaction_types() {
591        let similar = InteractionType::SimilarPatterns;
592        let sharing = InteractionType::MemorySharing;
593        let producer_consumer = InteractionType::ProducerConsumer;
594
595        // Test that all interaction types can be created
596        // (Mainly for coverage, these are simple enums)
597        let _interactions = [similar, sharing, producer_consumer];
598    }
599
600    #[test]
601    fn test_bottleneck_types_complete() {
602        let types = [
603            BottleneckType::HighFrequencySmallAllocation,
604            BottleneckType::LargeAllocationSpike,
605            BottleneckType::MemoryLeak,
606            BottleneckType::ThreadContention,
607            BottleneckType::FragmentationRisk,
608        ];
609
610        // Test that all bottleneck types can be created
611        assert_eq!(types.len(), 5);
612    }
613
614    #[test]
615    fn test_analysis_summary_fields() {
616        let summary = AnalysisSummary {
617            total_threads: 5,
618            total_allocations: 1000,
619            total_deallocations: 950,
620            peak_memory_usage: 16384,
621            total_memory_allocated: 32768,
622            unique_call_stacks: 25,
623            analysis_duration_ms: 500,
624            sampling_effectiveness: 85.5,
625        };
626
627        assert_eq!(summary.total_threads, 5);
628        assert_eq!(summary.total_allocations, 1000);
629        assert_eq!(summary.total_deallocations, 950);
630        assert_eq!(summary.peak_memory_usage, 16384);
631        assert_eq!(summary.total_memory_allocated, 32768);
632        assert_eq!(summary.unique_call_stacks, 25);
633        assert_eq!(summary.analysis_duration_ms, 500);
634        assert_eq!(summary.sampling_effectiveness, 85.5);
635    }
636
637    #[test]
638    fn test_complex_analysis_workflow() {
639        let mut analysis = LockfreeAnalysis::new();
640        let start_time = Instant::now();
641
642        // Build a complex analysis scenario
643        analysis
644            .thread_stats
645            .insert(1, create_test_thread_stats(1, 1000, 8192));
646        analysis
647            .thread_stats
648            .insert(2, create_test_thread_stats(2, 500, 4096));
649        analysis
650            .thread_stats
651            .insert(3, create_test_thread_stats(3, 2000, 16384));
652
653        analysis.hottest_call_stacks.push(HotCallStack {
654            call_stack_hash: 12345,
655            total_frequency: 1000,
656            total_size: 16384,
657            impact_score: 16384000,
658            threads: vec![1, 2, 3],
659        });
660
661        analysis.thread_interactions.push(ThreadInteraction {
662            thread_a: 1,
663            thread_b: 2,
664            shared_patterns: vec![12345],
665            interaction_strength: 75,
666            interaction_type: InteractionType::SimilarPatterns,
667        });
668
669        analysis.memory_peaks.push(MemoryPeak {
670            timestamp: 1000000,
671            thread_id: 3,
672            memory_usage: 16384,
673            active_allocations: 1000,
674            triggering_call_stack: 12345,
675        });
676
677        analysis
678            .performance_bottlenecks
679            .push(PerformanceBottleneck {
680                bottleneck_type: BottleneckType::HighFrequencySmallAllocation,
681                thread_id: 3,
682                call_stack_hash: 12345,
683                severity: 0.9,
684                description: "Very high allocation frequency".to_string(),
685                suggestion: "Consider object pooling".to_string(),
686            });
687
688        analysis.calculate_summary(start_time);
689
690        // Verify complex analysis state
691        assert_eq!(analysis.summary.total_threads, 3);
692        assert_eq!(analysis.summary.total_allocations, 3500);
693        assert!(analysis.get_most_active_threads(1)[0].1 == 2000); // Thread 3 most active
694        assert!(analysis.get_highest_memory_threads(1)[0].1 == 16384); // Thread 3 highest memory
695        assert_eq!(analysis.get_critical_bottlenecks(0.8).len(), 1);
696        assert_eq!(analysis.hottest_call_stacks.len(), 1);
697        assert_eq!(analysis.thread_interactions.len(), 1);
698        assert_eq!(analysis.memory_peaks.len(), 1);
699    }
700
701    #[test]
702    fn test_edge_cases() {
703        let mut analysis = LockfreeAnalysis::new();
704
705        // Test with zero allocations thread
706        let empty_stats = ThreadStats {
707            thread_id: 99,
708            total_allocations: 0,
709            total_deallocations: 0,
710            peak_memory: 0,
711            total_allocated: 0,
712            allocation_frequency: HashMap::new(),
713            avg_allocation_size: 0.0,
714            timeline: Vec::new(),
715        };
716        analysis.thread_stats.insert(99, empty_stats);
717
718        let start_time = Instant::now();
719        analysis.calculate_summary(start_time);
720
721        assert_eq!(analysis.summary.total_threads, 1);
722        assert_eq!(analysis.summary.total_allocations, 0);
723        assert_eq!(analysis.summary.sampling_effectiveness, 0.0);
724
725        // Test getting active threads with no activity
726        let active = analysis.get_most_active_threads(10);
727        assert_eq!(active.len(), 1);
728        assert_eq!(active[0].1, 0); // Zero allocations
729    }
730
731    #[test]
732    fn test_serialization_deserialization() {
733        // Test that structures can be serialized (important for data export)
734        let analysis = LockfreeAnalysis::new();
735
736        // This will compile if Serialize/Deserialize are properly implemented
737        let _json = serde_json::to_string(&analysis);
738
739        let event = AllocationEvent {
740            timestamp: 1000,
741            ptr: 0x1000,
742            size: 1024,
743            call_stack_hash: 12345,
744            event_type: EventType::Allocation,
745            thread_id: 1,
746        };
747
748        let _event_json = serde_json::to_string(&event);
749    }
750}