memscope_rs/unified/strategies/
single_thread.rs

1// Single-Thread Memory Tracking Strategy
2// Optimized implementation for single-threaded applications
3// Provides zero-overhead tracking with direct global storage
4
5use crate::core::tracker::{get_tracker, MemoryTracker as CoreMemoryTracker};
6use crate::lockfree::aggregator::LockfreeAggregator;
7use crate::unified::tracking_dispatcher::{
8    MemoryTracker, TrackerConfig, TrackerError, TrackerStatistics, TrackerType,
9};
10use std::collections::HashMap;
11use std::sync::Mutex;
12use std::time::Instant;
13use tracing::{debug, info, warn};
14
15/// Single-threaded memory tracking strategy
16/// Optimized for applications with single execution thread
17/// Uses direct global storage for maximum performance
18pub struct SingleThreadStrategy {
19    /// Configuration for this strategy instance
20    config: Option<TrackerConfig>,
21    /// Current tracking state
22    tracking_state: TrackingState,
23    /// Memory allocation records
24    allocation_records: Mutex<Vec<AllocationRecord>>,
25    /// Performance metrics
26    metrics: PerformanceMetrics,
27    /// Integration with existing aggregator
28    #[allow(dead_code)]
29    aggregator: Option<LockfreeAggregator>,
30    /// Integration with core global tracker
31    core_tracker: Option<std::sync::Arc<CoreMemoryTracker>>,
32}
33
34/// Current state of tracking session
35#[derive(Debug, Clone, PartialEq)]
36enum TrackingState {
37    /// Strategy not yet initialized
38    Uninitialized,
39    /// Strategy initialized but not tracking
40    Initialized,
41    /// Currently tracking allocations
42    Active,
43    /// Tracking stopped, data available for collection
44    Stopped,
45}
46
47/// Individual memory allocation record
48/// Stores comprehensive tracking information for single allocation
49#[derive(Debug, Clone)]
50struct AllocationRecord {
51    /// Unique allocation identifier
52    id: u64,
53    /// Memory pointer address
54    ptr: usize,
55    /// Allocated size in bytes
56    size: usize,
57    /// Variable name (if available)
58    var_name: Option<String>,
59    /// Type information
60    type_name: String,
61    /// Allocation timestamp (nanoseconds)
62    timestamp_alloc: u64,
63    /// Deallocation timestamp (if deallocated)
64    timestamp_dealloc: Option<u64>,
65    /// Stack trace at allocation
66    _stack_trace: Vec<String>,
67    /// Additional metadata
68    _metadata: HashMap<String, String>,
69}
70
71/// Performance metrics for single-thread strategy
72#[derive(Debug, Clone)]
73struct PerformanceMetrics {
74    /// Total allocations tracked
75    total_allocations: u64,
76    /// Total bytes allocated
77    total_bytes_allocated: u64,
78    /// Peak concurrent allocations
79    peak_allocations: u64,
80    /// Strategy initialization time (microseconds)
81    init_time_us: u64,
82    /// Total tracking duration (microseconds)
83    tracking_duration_us: u64,
84    /// Memory overhead of tracking (bytes)
85    overhead_bytes: usize,
86    /// Average allocation tracking time (nanoseconds)
87    avg_allocation_time_ns: f64,
88}
89
90impl Default for PerformanceMetrics {
91    /// Initialize performance metrics with zero values
92    fn default() -> Self {
93        Self {
94            total_allocations: 0,
95            total_bytes_allocated: 0,
96            peak_allocations: 0,
97            init_time_us: 0,
98            tracking_duration_us: 0,
99            overhead_bytes: 0,
100            avg_allocation_time_ns: 0.0,
101        }
102    }
103}
104
105impl SingleThreadStrategy {
106    /// Create new single-thread strategy instance
107    /// Returns uninitialized strategy ready for configuration
108    pub fn new() -> Self {
109        debug!("Creating new single-thread strategy");
110
111        Self {
112            config: None,
113            tracking_state: TrackingState::Uninitialized,
114            allocation_records: Mutex::new(Vec::new()),
115            metrics: PerformanceMetrics::default(),
116            aggregator: None,
117            core_tracker: None,
118        }
119    }
120
121    /// Create strategy with integration to existing aggregator
122    /// Enables compatibility with existing lockfree infrastructure
123    pub fn with_aggregator(output_dir: std::path::PathBuf) -> Self {
124        debug!("Creating single-thread strategy with aggregator integration");
125
126        let aggregator = LockfreeAggregator::new(output_dir);
127
128        Self {
129            config: None,
130            tracking_state: TrackingState::Uninitialized,
131            allocation_records: Mutex::new(Vec::new()),
132            metrics: PerformanceMetrics::default(),
133            aggregator: Some(aggregator),
134            core_tracker: None,
135        }
136    }
137
138    /// Create strategy with full core tracker integration
139    /// Enables seamless integration with existing global tracking system
140    pub fn with_core_integration() -> Self {
141        debug!("Creating single-thread strategy with core tracker integration");
142
143        let core_tracker = get_tracker();
144
145        Self {
146            config: None,
147            tracking_state: TrackingState::Uninitialized,
148            allocation_records: Mutex::new(Vec::new()),
149            metrics: PerformanceMetrics::default(),
150            aggregator: None,
151            core_tracker: Some(core_tracker),
152        }
153    }
154
155    /// Create strategy with both aggregator and core integration
156    /// Provides maximum compatibility with existing infrastructure
157    pub fn with_full_integration(output_dir: std::path::PathBuf) -> Self {
158        debug!("Creating single-thread strategy with full integration");
159
160        let aggregator = LockfreeAggregator::new(output_dir);
161        let core_tracker = get_tracker();
162
163        Self {
164            config: None,
165            tracking_state: TrackingState::Uninitialized,
166            allocation_records: Mutex::new(Vec::new()),
167            metrics: PerformanceMetrics::default(),
168            aggregator: Some(aggregator),
169            core_tracker: Some(core_tracker),
170        }
171    }
172
173    /// Track new memory allocation
174    /// Records allocation details with minimal overhead
175    pub fn track_allocation(
176        &mut self,
177        ptr: usize,
178        size: usize,
179        var_name: Option<String>,
180        type_name: String,
181    ) -> Result<(), TrackerError> {
182        if self.tracking_state != TrackingState::Active {
183            return Err(TrackerError::StartFailed {
184                reason: "Strategy not in active tracking state".to_string(),
185            });
186        }
187
188        let start_time = Instant::now();
189
190        // Generate unique allocation ID
191        let id = self.metrics.total_allocations + 1;
192
193        // Capture stack trace (simplified for performance)
194        let stack_trace = self.capture_stack_trace();
195
196        // Create allocation record
197        let record = AllocationRecord {
198            id,
199            ptr,
200            size,
201            var_name,
202            type_name,
203            timestamp_alloc: self.get_timestamp_ns(),
204            timestamp_dealloc: None,
205            _stack_trace: stack_trace,
206            _metadata: HashMap::new(),
207        };
208
209        // Store record
210        {
211            let mut records = self.allocation_records.lock().unwrap();
212            records.push(record);
213        } // Explicit scope to release lock quickly
214
215        // Integrate with core tracker if available
216        if let Some(core_tracker) = &self.core_tracker {
217            // Track in core system using basic allocation tracking
218            if let Err(e) = core_tracker.track_allocation(ptr, size) {
219                warn!("Core tracker integration failed: {:?}", e);
220                // Continue with unified tracking even if core integration fails
221            }
222        }
223
224        // Update metrics
225        self.metrics.total_allocations += 1;
226        self.metrics.total_bytes_allocated += size as u64;
227
228        // Update peak allocations
229        let current_allocations = self.allocation_records.lock().unwrap().len() as u64;
230        if current_allocations > self.metrics.peak_allocations {
231            self.metrics.peak_allocations = current_allocations;
232        }
233
234        // Update average allocation time
235        let allocation_time_ns = start_time.elapsed().as_nanos() as f64;
236        let weight = 0.1; // Exponential moving average
237        self.metrics.avg_allocation_time_ns =
238            (1.0 - weight) * self.metrics.avg_allocation_time_ns + weight * allocation_time_ns;
239
240        debug!(
241            "Tracked allocation: ptr={:x}, size={}, id={}",
242            ptr, size, id
243        );
244        Ok(())
245    }
246
247    /// Track memory deallocation
248    /// Updates existing allocation record with deallocation timestamp
249    pub fn track_deallocation(&mut self, ptr: usize) -> Result<(), TrackerError> {
250        if self.tracking_state != TrackingState::Active {
251            return Err(TrackerError::StartFailed {
252                reason: "Strategy not in active tracking state".to_string(),
253            });
254        }
255
256        let timestamp = self.get_timestamp_ns();
257
258        // Find and update allocation record
259        let mut records = self.allocation_records.lock().unwrap();
260        if let Some(record) = records
261            .iter_mut()
262            .find(|r| r.ptr == ptr && r.timestamp_dealloc.is_none())
263        {
264            record.timestamp_dealloc = Some(timestamp);
265            debug!("Tracked deallocation: ptr={ptr:x}, id={}", record.id);
266            Ok(())
267        } else {
268            warn!("Deallocation tracked for unknown pointer: {ptr:x}",);
269            Err(TrackerError::DataCollectionFailed {
270                reason: format!("Unknown pointer for deallocation: {ptr:x}"),
271            })
272        }
273    }
274
275    /// Capture simplified stack trace for allocation context
276    /// Optimized for single-threaded performance
277    fn capture_stack_trace(&self) -> Vec<String> {
278        // Simplified stack trace for performance
279        // In production, this would integrate with backtrace crate
280        vec![
281            "single_thread_strategy::track_allocation".to_string(),
282            "application_code::allocate_memory".to_string(),
283        ]
284    }
285
286    /// Get high-precision timestamp in nanoseconds
287    fn get_timestamp_ns(&self) -> u64 {
288        // Use a simple incrementing counter for tests to avoid system time issues
289        // In production, this would use proper high-resolution timing
290        #[cfg(test)]
291        {
292            use std::sync::atomic::{AtomicU64, Ordering};
293            static COUNTER: AtomicU64 = AtomicU64::new(1_000_000_000); // Start at 1 second
294            COUNTER.fetch_add(1000, Ordering::Relaxed) // Increment by 1 microsecond
295        }
296        #[cfg(not(test))]
297        {
298            std::time::SystemTime::now()
299                .duration_since(std::time::UNIX_EPOCH)
300                .map(|d| d.as_nanos() as u64)
301                .unwrap_or(0)
302        }
303    }
304
305    /// Calculate current memory overhead of tracking system
306    fn calculate_overhead(&self) -> usize {
307        let records = self.allocation_records.lock().unwrap();
308        let record_overhead = records.len() * std::mem::size_of::<AllocationRecord>();
309        let base_overhead = std::mem::size_of::<Self>();
310
311        record_overhead + base_overhead
312    }
313
314    /// Convert allocation records to JSON format compatible with existing exports
315    fn export_as_json(&self) -> Result<String, TrackerError> {
316        // Clone records to avoid holding lock during serialization
317        let records = {
318            let records_guard = self.allocation_records.lock().unwrap();
319            records_guard.clone()
320        };
321
322        // Create JSON structure compatible with existing format
323        let mut allocations = Vec::new();
324
325        for record in records.iter() {
326            let mut allocation = serde_json::Map::new();
327
328            allocation.insert(
329                "ptr".to_string(),
330                serde_json::Value::String(format!("{:x}", record.ptr)),
331            );
332            allocation.insert(
333                "size".to_string(),
334                serde_json::Value::Number(serde_json::Number::from(record.size)),
335            );
336            allocation.insert(
337                "timestamp_alloc".to_string(),
338                serde_json::Value::String(record.timestamp_alloc.to_string()),
339            );
340
341            if let Some(var_name) = &record.var_name {
342                allocation.insert(
343                    "var_name".to_string(),
344                    serde_json::Value::String(var_name.clone()),
345                );
346            }
347
348            allocation.insert(
349                "type_name".to_string(),
350                serde_json::Value::String(record.type_name.clone()),
351            );
352
353            if let Some(timestamp_dealloc) = record.timestamp_dealloc {
354                allocation.insert(
355                    "timestamp_dealloc".to_string(),
356                    serde_json::Value::String(timestamp_dealloc.to_string()),
357                );
358            }
359
360            // Add tracking strategy metadata
361            allocation.insert(
362                "tracking_strategy".to_string(),
363                serde_json::Value::String("single_thread".to_string()),
364            );
365
366            allocations.push(serde_json::Value::Object(allocation));
367        }
368
369        let mut output = serde_json::Map::new();
370        output.insert(
371            "allocations".to_string(),
372            serde_json::Value::Array(allocations),
373        );
374        output.insert(
375            "strategy_metadata".to_string(),
376            serde_json::json!({
377                "strategy_type": "single_thread",
378                "total_allocations": self.metrics.total_allocations,
379                "total_bytes": self.metrics.total_bytes_allocated,
380                "tracking_duration_us": self.metrics.tracking_duration_us,
381                "overhead_bytes": self.calculate_overhead()
382            }),
383        );
384
385        serde_json::to_string_pretty(&output).map_err(|e| TrackerError::DataCollectionFailed {
386            reason: format!("JSON serialization failed: {e}"),
387        })
388    }
389}
390
391impl MemoryTracker for SingleThreadStrategy {
392    /// Initialize strategy with provided configuration
393    /// Sets up tracking infrastructure and validates configuration
394    fn initialize(&mut self, config: TrackerConfig) -> Result<(), TrackerError> {
395        let start_time = Instant::now();
396
397        debug!(
398            "Initializing single-thread strategy with config: {:?}",
399            config
400        );
401
402        // Validate configuration
403        if config.sample_rate < 0.0 || config.sample_rate > 1.0 {
404            return Err(TrackerError::InvalidConfiguration {
405                reason: "Sample rate must be between 0.0 and 1.0".to_string(),
406            });
407        }
408
409        if config.max_overhead_mb == 0 {
410            return Err(TrackerError::InvalidConfiguration {
411                reason: "Maximum overhead must be greater than 0".to_string(),
412            });
413        }
414
415        // Store configuration
416        self.config = Some(config);
417
418        // Initialize allocation storage with reasonable capacity
419        let initial_capacity = 1000; // Expect ~1000 allocations initially
420        self.allocation_records = Mutex::new(Vec::with_capacity(initial_capacity));
421
422        // Update state and metrics
423        self.tracking_state = TrackingState::Initialized;
424        self.metrics.init_time_us = start_time.elapsed().as_micros() as u64;
425
426        info!(
427            "Single-thread strategy initialized in {}μs",
428            self.metrics.init_time_us
429        );
430        Ok(())
431    }
432
433    /// Start active memory tracking
434    /// Transitions strategy to active state and begins collecting data
435    fn start_tracking(&mut self) -> Result<(), TrackerError> {
436        match &self.tracking_state {
437            TrackingState::Initialized => {
438                debug!("Starting single-thread tracking");
439
440                self.tracking_state = TrackingState::Active;
441
442                // Reset metrics for new session
443                self.metrics.total_allocations = 0;
444                self.metrics.total_bytes_allocated = 0;
445                self.metrics.peak_allocations = 0;
446
447                // Clear previous allocation records
448                self.allocation_records.lock().unwrap().clear();
449
450                info!("Single-thread tracking started successfully");
451                Ok(())
452            }
453            TrackingState::Active => {
454                warn!("Tracking already active");
455                Ok(()) // Already active, not an error
456            }
457            other_state => Err(TrackerError::StartFailed {
458                reason: format!("Cannot start tracking from state: {other_state:?}"),
459            }),
460        }
461    }
462
463    /// Stop tracking and collect all data
464    /// Returns serialized tracking data in compatible format
465    fn stop_tracking(&mut self) -> Result<Vec<u8>, TrackerError> {
466        match &self.tracking_state {
467            TrackingState::Active => {
468                debug!("Stopping single-thread tracking");
469
470                self.tracking_state = TrackingState::Stopped;
471
472                // Update final metrics
473                self.metrics.overhead_bytes = self.calculate_overhead();
474
475                // Export data in JSON format
476                let json_data = self.export_as_json()?;
477
478                info!(
479                    "Single-thread tracking stopped, collected {} allocations",
480                    self.metrics.total_allocations
481                );
482
483                Ok(json_data.into_bytes())
484            }
485            TrackingState::Stopped => {
486                // Already stopped, return cached data
487                let json_data = self.export_as_json()?;
488                Ok(json_data.into_bytes())
489            }
490            other_state => Err(TrackerError::DataCollectionFailed {
491                reason: format!("Cannot stop tracking from state: {other_state:?}"),
492            }),
493        }
494    }
495
496    /// Get current tracking statistics
497    /// Provides real-time performance and usage metrics
498    fn get_statistics(&self) -> TrackerStatistics {
499        TrackerStatistics {
500            allocations_tracked: self.metrics.total_allocations,
501            memory_tracked_bytes: self.metrics.total_bytes_allocated,
502            overhead_bytes: self.calculate_overhead() as u64,
503            tracking_duration_ms: self.metrics.tracking_duration_us / 1000,
504        }
505    }
506
507    /// Check if strategy is currently active
508    fn is_active(&self) -> bool {
509        self.tracking_state == TrackingState::Active
510    }
511
512    /// Get strategy type identifier
513    fn tracker_type(&self) -> TrackerType {
514        TrackerType::SingleThread
515    }
516}
517
518impl Default for SingleThreadStrategy {
519    /// Create strategy with default configuration
520    fn default() -> Self {
521        Self::new()
522    }
523}
524
525#[cfg(test)]
526mod tests {
527    use super::*;
528    use std::collections::HashMap;
529
530    #[test]
531    fn test_strategy_creation() {
532        let strategy = SingleThreadStrategy::new();
533        assert_eq!(strategy.tracking_state, TrackingState::Uninitialized);
534        assert!(!strategy.is_active());
535        assert_eq!(strategy.tracker_type(), TrackerType::SingleThread);
536    }
537
538    #[test]
539    fn test_strategy_initialization() {
540        let mut strategy = SingleThreadStrategy::new();
541        let config = TrackerConfig {
542            sample_rate: 1.0,
543            max_overhead_mb: 32,
544            thread_affinity: None,
545            custom_params: HashMap::new(),
546        };
547
548        let result = strategy.initialize(config);
549        assert!(result.is_ok());
550        assert_eq!(strategy.tracking_state, TrackingState::Initialized);
551    }
552
553    #[test]
554    fn test_invalid_configuration() {
555        let mut strategy = SingleThreadStrategy::new();
556        let config = TrackerConfig {
557            sample_rate: 1.5, // Invalid: > 1.0
558            max_overhead_mb: 32,
559            thread_affinity: None,
560            custom_params: HashMap::new(),
561        };
562
563        let result = strategy.initialize(config);
564        assert!(result.is_err());
565        match result.unwrap_err() {
566            TrackerError::InvalidConfiguration { reason } => {
567                assert!(reason.contains("Sample rate"));
568            }
569            _ => panic!("Expected InvalidConfiguration error"),
570        }
571    }
572
573    #[test]
574    fn test_tracking_lifecycle() {
575        let mut strategy = SingleThreadStrategy::new();
576        let config = TrackerConfig::default();
577
578        // Initialize
579        assert!(strategy.initialize(config).is_ok());
580
581        // Start tracking
582        assert!(strategy.start_tracking().is_ok());
583        assert!(strategy.is_active());
584
585        // Track allocation
586        let result = strategy.track_allocation(
587            0x1000,
588            128,
589            Some("test_var".to_string()),
590            "TestType".to_string(),
591        );
592        assert!(result.is_ok());
593
594        // Check statistics
595        let stats = strategy.get_statistics();
596        assert_eq!(stats.allocations_tracked, 1);
597        assert_eq!(stats.memory_tracked_bytes, 128);
598
599        // Track deallocation
600        assert!(strategy.track_deallocation(0x1000).is_ok());
601
602        // Stop tracking
603        let data = strategy.stop_tracking();
604        assert!(data.is_ok());
605        assert!(!strategy.is_active());
606    }
607
608    #[test]
609    fn test_allocation_tracking() {
610        let mut strategy = SingleThreadStrategy::new();
611        strategy.initialize(TrackerConfig::default()).unwrap();
612        strategy.start_tracking().unwrap();
613
614        // Track multiple allocations
615        for i in 0..10 {
616            let ptr = 0x1000 + i * 0x100;
617            let size = 64 + i * 8;
618
619            let result = strategy.track_allocation(
620                ptr,
621                size,
622                Some(format!("var_{i}")),
623                "TestType".to_string(),
624            );
625            assert!(result.is_ok());
626        }
627
628        let stats = strategy.get_statistics();
629        assert_eq!(stats.allocations_tracked, 10);
630        assert_eq!(
631            stats.memory_tracked_bytes,
632            64 * 10 + 8 * (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9)
633        );
634    }
635
636    #[test]
637    fn test_json_export_format() {
638        let mut strategy = SingleThreadStrategy::new();
639        strategy.initialize(TrackerConfig::default()).unwrap();
640        strategy.start_tracking().unwrap();
641
642        // Track one allocation
643        strategy
644            .track_allocation(
645                0x1000,
646                256,
647                Some("test_variable".to_string()),
648                "TestStruct".to_string(),
649            )
650            .unwrap();
651
652        let data = strategy.stop_tracking().unwrap();
653        let json_str = String::from_utf8(data).unwrap();
654
655        // Verify JSON structure
656        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
657        assert!(parsed["allocations"].is_array());
658        assert!(parsed["strategy_metadata"].is_object());
659
660        let allocations = parsed["allocations"].as_array().unwrap();
661        assert_eq!(allocations.len(), 1);
662
663        let first_alloc = &allocations[0];
664        assert_eq!(first_alloc["ptr"].as_str().unwrap(), "1000");
665        assert_eq!(first_alloc["size"].as_u64().unwrap(), 256);
666        assert_eq!(first_alloc["var_name"].as_str().unwrap(), "test_variable");
667        assert_eq!(first_alloc["type_name"].as_str().unwrap(), "TestStruct");
668        assert_eq!(
669            first_alloc["tracking_strategy"].as_str().unwrap(),
670            "single_thread"
671        );
672    }
673
674    #[test]
675    fn test_performance_metrics() {
676        let mut strategy = SingleThreadStrategy::new();
677        strategy.initialize(TrackerConfig::default()).unwrap();
678        strategy.start_tracking().unwrap();
679
680        // Track allocations to generate metrics
681        for i in 0..100 {
682            strategy
683                .track_allocation(0x1000 + i * 0x10, 64, None, "TestType".to_string())
684                .unwrap();
685        }
686
687        let stats = strategy.get_statistics();
688        assert_eq!(stats.allocations_tracked, 100);
689        assert_eq!(stats.memory_tracked_bytes, 6400);
690        assert!(stats.overhead_bytes > 0);
691
692        // Check that average allocation time is reasonable
693        assert!(strategy.metrics.avg_allocation_time_ns > 0.0);
694        assert!(strategy.metrics.avg_allocation_time_ns < 1_000_000.0); // Less than 1ms
695    }
696}