1use 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
15pub struct SingleThreadStrategy {
19 config: Option<TrackerConfig>,
21 tracking_state: TrackingState,
23 allocation_records: Mutex<Vec<AllocationRecord>>,
25 metrics: PerformanceMetrics,
27 #[allow(dead_code)]
29 aggregator: Option<LockfreeAggregator>,
30 core_tracker: Option<std::sync::Arc<CoreMemoryTracker>>,
32}
33
34#[derive(Debug, Clone, PartialEq)]
36enum TrackingState {
37 Uninitialized,
39 Initialized,
41 Active,
43 Stopped,
45}
46
47#[derive(Debug, Clone)]
50struct AllocationRecord {
51 id: u64,
53 ptr: usize,
55 size: usize,
57 var_name: Option<String>,
59 type_name: String,
61 timestamp_alloc: u64,
63 timestamp_dealloc: Option<u64>,
65 _stack_trace: Vec<String>,
67 _metadata: HashMap<String, String>,
69}
70
71#[derive(Debug, Clone)]
73struct PerformanceMetrics {
74 total_allocations: u64,
76 total_bytes_allocated: u64,
78 peak_allocations: u64,
80 init_time_us: u64,
82 tracking_duration_us: u64,
84 overhead_bytes: usize,
86 avg_allocation_time_ns: f64,
88}
89
90impl Default for PerformanceMetrics {
91 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 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 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 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 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 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 let id = self.metrics.total_allocations + 1;
192
193 let stack_trace = self.capture_stack_trace();
195
196 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 {
211 let mut records = self.allocation_records.lock().unwrap();
212 records.push(record);
213 } if let Some(core_tracker) = &self.core_tracker {
217 if let Err(e) = core_tracker.track_allocation(ptr, size) {
219 warn!("Core tracker integration failed: {:?}", e);
220 }
222 }
223
224 self.metrics.total_allocations += 1;
226 self.metrics.total_bytes_allocated += size as u64;
227
228 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 let allocation_time_ns = start_time.elapsed().as_nanos() as f64;
236 let weight = 0.1; 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 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 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 fn capture_stack_trace(&self) -> Vec<String> {
278 vec![
281 "single_thread_strategy::track_allocation".to_string(),
282 "application_code::allocate_memory".to_string(),
283 ]
284 }
285
286 fn get_timestamp_ns(&self) -> u64 {
288 #[cfg(test)]
291 {
292 use std::sync::atomic::{AtomicU64, Ordering};
293 static COUNTER: AtomicU64 = AtomicU64::new(1_000_000_000); COUNTER.fetch_add(1000, Ordering::Relaxed) }
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 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 fn export_as_json(&self) -> Result<String, TrackerError> {
316 let records = {
318 let records_guard = self.allocation_records.lock().unwrap();
319 records_guard.clone()
320 };
321
322 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 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 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 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 self.config = Some(config);
417
418 let initial_capacity = 1000; self.allocation_records = Mutex::new(Vec::with_capacity(initial_capacity));
421
422 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 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 self.metrics.total_allocations = 0;
444 self.metrics.total_bytes_allocated = 0;
445 self.metrics.peak_allocations = 0;
446
447 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(()) }
457 other_state => Err(TrackerError::StartFailed {
458 reason: format!("Cannot start tracking from state: {other_state:?}"),
459 }),
460 }
461 }
462
463 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 self.metrics.overhead_bytes = self.calculate_overhead();
474
475 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 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 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 fn is_active(&self) -> bool {
509 self.tracking_state == TrackingState::Active
510 }
511
512 fn tracker_type(&self) -> TrackerType {
514 TrackerType::SingleThread
515 }
516}
517
518impl Default for SingleThreadStrategy {
519 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, 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 assert!(strategy.initialize(config).is_ok());
580
581 assert!(strategy.start_tracking().is_ok());
583 assert!(strategy.is_active());
584
585 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 let stats = strategy.get_statistics();
596 assert_eq!(stats.allocations_tracked, 1);
597 assert_eq!(stats.memory_tracked_bytes, 128);
598
599 assert!(strategy.track_deallocation(0x1000).is_ok());
601
602 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 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 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 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 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 assert!(strategy.metrics.avg_allocation_time_ns > 0.0);
694 assert!(strategy.metrics.avg_allocation_time_ns < 1_000_000.0); }
696}