1#[cfg(feature = "profiling_memory")]
40use crate::CoreResult;
41#[cfg(feature = "profiling_memory")]
42use std::collections::HashMap;
43#[cfg(feature = "profiling_memory")]
44use std::sync::atomic::{AtomicUsize, Ordering};
45
46#[cfg(feature = "profiling_memory")]
51static TRACKED_ALLOCATED: AtomicUsize = AtomicUsize::new(0);
52#[cfg(feature = "profiling_memory")]
53static TRACKED_PEAK: AtomicUsize = AtomicUsize::new(0);
54
55#[cfg(feature = "profiling_memory")]
57fn record_allocation(size: usize) {
58 let prev = TRACKED_ALLOCATED.fetch_add(size, Ordering::Relaxed);
59 let new_total = prev + size;
60 let mut current_peak = TRACKED_PEAK.load(Ordering::Relaxed);
62 while new_total > current_peak {
63 match TRACKED_PEAK.compare_exchange_weak(
64 current_peak,
65 new_total,
66 Ordering::Relaxed,
67 Ordering::Relaxed,
68 ) {
69 Ok(_) => break,
70 Err(actual) => current_peak = actual,
71 }
72 }
73}
74
75#[cfg(feature = "profiling_memory")]
77fn record_deallocation(size: usize) {
78 TRACKED_ALLOCATED.fetch_sub(size, Ordering::Relaxed);
79}
80
81#[cfg(feature = "profiling_memory")]
83fn get_tracked_allocated() -> usize {
84 TRACKED_ALLOCATED.load(Ordering::Relaxed)
85}
86
87#[cfg(feature = "profiling_memory")]
93#[derive(Debug, Clone, Default)]
94struct OsMemoryInfo {
95 resident: usize,
97 virtual_size: usize,
99}
100
101#[cfg(all(feature = "profiling_memory", target_os = "macos"))]
103fn read_os_memory_info() -> CoreResult<OsMemoryInfo> {
104 use std::mem;
107
108 #[repr(C)]
110 #[derive(Default)]
111 struct MachTaskBasicInfo {
112 virtual_size: u64, resident_size: u64, resident_size_max: u64, user_time: [u32; 2], system_time: [u32; 2], policy: i32, suspend_count: i32, }
120
121 const MACH_TASK_BASIC_INFO: u32 = 20;
122 const MACH_TASK_BASIC_INFO_COUNT: u32 =
124 (mem::size_of::<MachTaskBasicInfo>() / mem::size_of::<u32>()) as u32;
125
126 extern "C" {
127 fn mach_task_self() -> u32;
128 fn task_info(
129 target_task: u32,
130 flavor: u32,
131 task_info_out: *mut MachTaskBasicInfo,
132 task_info_count: *mut u32,
133 ) -> i32;
134 }
135
136 let mut info = MachTaskBasicInfo::default();
137 let mut count = MACH_TASK_BASIC_INFO_COUNT;
138
139 let kr = unsafe {
142 task_info(
143 mach_task_self(),
144 MACH_TASK_BASIC_INFO,
145 &mut info as *mut MachTaskBasicInfo,
146 &mut count,
147 )
148 };
149
150 if kr != 0 {
152 return Err(crate::CoreError::ConfigError(
153 crate::error::ErrorContext::new(format!("task_info failed with kern_return: {}", kr)),
154 ));
155 }
156
157 Ok(OsMemoryInfo {
158 resident: info.resident_size as usize,
159 virtual_size: info.virtual_size as usize,
160 })
161}
162
163#[cfg(all(feature = "profiling_memory", target_os = "linux"))]
165fn read_os_memory_info() -> CoreResult<OsMemoryInfo> {
166 use std::fs;
167
168 let statm = fs::read_to_string("/proc/self/statm").map_err(|e| {
171 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
172 "Failed to read /proc/self/statm: {}",
173 e
174 )))
175 })?;
176
177 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
178 let page_size = if page_size <= 0 {
179 4096
180 } else {
181 page_size as usize
182 };
183
184 let parts: Vec<&str> = statm.trim().split_whitespace().collect();
185 if parts.len() < 2 {
186 return Err(crate::CoreError::ConfigError(
187 crate::error::ErrorContext::new("Invalid /proc/self/statm format".to_string()),
188 ));
189 }
190
191 let virtual_pages: usize = parts[0].parse().map_err(|e| {
192 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
193 "Failed to parse virtual size from /proc/self/statm: {}",
194 e
195 )))
196 })?;
197
198 let resident_pages: usize = parts[1].parse().map_err(|e| {
199 crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
200 "Failed to parse resident size from /proc/self/statm: {}",
201 e
202 )))
203 })?;
204
205 Ok(OsMemoryInfo {
206 resident: resident_pages * page_size,
207 virtual_size: virtual_pages * page_size,
208 })
209}
210
211#[cfg(all(
213 feature = "profiling_memory",
214 not(target_os = "macos"),
215 not(target_os = "linux")
216))]
217fn read_os_memory_info() -> CoreResult<OsMemoryInfo> {
218 let allocated = get_tracked_allocated();
220 Ok(OsMemoryInfo {
221 resident: allocated,
222 virtual_size: allocated,
223 })
224}
225
226#[cfg(feature = "profiling_memory")]
232#[derive(Debug, Clone)]
233pub struct MemoryStats {
234 pub allocated: usize,
236 pub resident: usize,
238 pub mapped: usize,
240 pub metadata: usize,
242 pub retained: usize,
244}
245
246#[cfg(feature = "profiling_memory")]
247impl MemoryStats {
248 pub fn current() -> CoreResult<Self> {
250 let os_info = read_os_memory_info()?;
251 let tracked = get_tracked_allocated();
252
253 let allocated = if tracked > 0 {
256 tracked
257 } else {
258 os_info.resident
259 };
260
261 let metadata = allocated / 50;
263
264 let retained = os_info.resident.saturating_sub(allocated);
266
267 Ok(Self {
268 allocated,
269 resident: os_info.resident,
270 mapped: os_info.virtual_size,
271 metadata,
272 retained,
273 })
274 }
275
276 pub fn overhead_ratio(&self) -> f64 {
278 if self.allocated == 0 {
279 0.0
280 } else {
281 self.metadata as f64 / self.allocated as f64
282 }
283 }
284
285 pub fn utilization_ratio(&self) -> f64 {
287 if self.resident == 0 {
288 0.0
289 } else {
290 self.allocated as f64 / self.resident as f64
291 }
292 }
293
294 pub fn format(&self) -> String {
296 format!(
297 "Memory Stats:\n\
298 - Allocated: {} MB\n\
299 - Resident: {} MB\n\
300 - Mapped: {} MB\n\
301 - Metadata: {} MB\n\
302 - Retained: {} MB\n\
303 - Overhead: {:.2}%\n\
304 - Utilization: {:.2}%",
305 self.allocated / 1_048_576,
306 self.resident / 1_048_576,
307 self.mapped / 1_048_576,
308 self.metadata / 1_048_576,
309 self.retained / 1_048_576,
310 self.overhead_ratio() * 100.0,
311 self.utilization_ratio() * 100.0
312 )
313 }
314}
315
316#[cfg(feature = "profiling_memory")]
322pub struct MemoryProfiler {
323 baseline: Option<MemoryStats>,
324}
325
326#[cfg(feature = "profiling_memory")]
327impl MemoryProfiler {
328 pub fn new() -> Self {
330 Self { baseline: None }
331 }
332
333 pub fn set_baseline(&mut self) -> CoreResult<()> {
335 self.baseline = Some(MemoryStats::current()?);
336 Ok(())
337 }
338
339 pub fn get_stats() -> CoreResult<MemoryStats> {
341 MemoryStats::current()
342 }
343
344 pub fn get_delta(&self) -> CoreResult<Option<MemoryDelta>> {
346 if let Some(ref baseline) = self.baseline {
347 let current = MemoryStats::current()?;
348 Ok(Some(MemoryDelta {
349 allocated_delta: current.allocated as i64 - baseline.allocated as i64,
350 resident_delta: current.resident as i64 - baseline.resident as i64,
351 mapped_delta: current.mapped as i64 - baseline.mapped as i64,
352 metadata_delta: current.metadata as i64 - baseline.metadata as i64,
353 retained_delta: current.retained as i64 - baseline.retained as i64,
354 }))
355 } else {
356 Ok(None)
357 }
358 }
359
360 pub fn print_stats() -> CoreResult<()> {
362 let stats = Self::get_stats()?;
363 println!("{}", stats.format());
364 Ok(())
365 }
366
367 pub fn track_allocation(size: usize) {
369 record_allocation(size);
370 }
371
372 pub fn track_deallocation(size: usize) {
374 record_deallocation(size);
375 }
376}
377
378#[cfg(feature = "profiling_memory")]
379impl Default for MemoryProfiler {
380 fn default() -> Self {
381 Self::new()
382 }
383}
384
385#[cfg(feature = "profiling_memory")]
391#[derive(Debug, Clone)]
392pub struct MemoryDelta {
393 pub allocated_delta: i64,
394 pub resident_delta: i64,
395 pub mapped_delta: i64,
396 pub metadata_delta: i64,
397 pub retained_delta: i64,
398}
399
400#[cfg(feature = "profiling_memory")]
401impl MemoryDelta {
402 pub fn format(&self) -> String {
404 format!(
405 "Memory Delta:\n\
406 - Allocated: {:+} MB\n\
407 - Resident: {:+} MB\n\
408 - Mapped: {:+} MB\n\
409 - Metadata: {:+} MB\n\
410 - Retained: {:+} MB",
411 self.allocated_delta / 1_048_576,
412 self.resident_delta / 1_048_576,
413 self.mapped_delta / 1_048_576,
414 self.metadata_delta / 1_048_576,
415 self.retained_delta / 1_048_576
416 )
417 }
418}
419
420#[cfg(feature = "profiling_memory")]
426pub struct AllocationTracker {
427 snapshots: Vec<(String, MemoryStats)>,
428}
429
430#[cfg(feature = "profiling_memory")]
431impl AllocationTracker {
432 pub fn new() -> Self {
434 Self {
435 snapshots: Vec::new(),
436 }
437 }
438
439 pub fn snapshot(&mut self, label: impl Into<String>) -> CoreResult<()> {
441 let stats = MemoryStats::current()?;
442 self.snapshots.push((label.into(), stats));
443 Ok(())
444 }
445
446 pub fn snapshots(&self) -> &[(String, MemoryStats)] {
448 &self.snapshots
449 }
450
451 pub fn analyze(&self) -> AllocationAnalysis {
453 if self.snapshots.is_empty() {
454 return AllocationAnalysis {
455 total_allocated: 0,
456 peak_allocated: 0,
457 total_snapshots: 0,
458 largest_increase: None,
459 patterns: HashMap::new(),
460 };
461 }
462
463 let mut peak_allocated = 0;
464 let mut largest_increase: Option<(String, i64)> = None;
465
466 for i in 0..self.snapshots.len() {
467 let (ref label, ref stats) = self.snapshots[i];
468
469 if stats.allocated > peak_allocated {
470 peak_allocated = stats.allocated;
471 }
472
473 if i > 0 {
474 let prev_stats = &self.snapshots[i - 1].1;
475 let increase = stats.allocated as i64 - prev_stats.allocated as i64;
476
477 if let Some((_, max_increase)) = largest_increase {
478 if increase > max_increase {
479 largest_increase = Some((label.clone(), increase));
480 }
481 } else {
482 largest_increase = Some((label.clone(), increase));
483 }
484 }
485 }
486
487 let last_allocated = self.snapshots.last().map(|(_, s)| s.allocated).unwrap_or(0);
488
489 AllocationAnalysis {
490 total_allocated: last_allocated,
491 peak_allocated,
492 total_snapshots: self.snapshots.len(),
493 largest_increase,
494 patterns: HashMap::new(),
495 }
496 }
497
498 pub fn clear(&mut self) {
500 self.snapshots.clear();
501 }
502}
503
504#[cfg(feature = "profiling_memory")]
505impl Default for AllocationTracker {
506 fn default() -> Self {
507 Self::new()
508 }
509}
510
511#[cfg(feature = "profiling_memory")]
513#[derive(Debug, Clone)]
514pub struct AllocationAnalysis {
515 pub total_allocated: usize,
516 pub peak_allocated: usize,
517 pub total_snapshots: usize,
518 pub largest_increase: Option<(String, i64)>,
519 pub patterns: HashMap<String, usize>,
520}
521
522#[cfg(feature = "profiling_memory")]
524pub fn enable_profiling() -> CoreResult<()> {
525 Ok(())
528}
529
530#[cfg(feature = "profiling_memory")]
532pub fn disable_profiling() -> CoreResult<()> {
533 TRACKED_ALLOCATED.store(0, Ordering::Relaxed);
535 TRACKED_PEAK.store(0, Ordering::Relaxed);
536 Ok(())
537}
538
539#[cfg(not(feature = "profiling_memory"))]
544use crate::CoreResult;
545
546#[cfg(not(feature = "profiling_memory"))]
547#[derive(Debug, Clone)]
548pub struct MemoryStats {
549 pub allocated: usize,
550 pub resident: usize,
551 pub mapped: usize,
552 pub metadata: usize,
553 pub retained: usize,
554}
555
556#[cfg(not(feature = "profiling_memory"))]
557impl MemoryStats {
558 pub fn current() -> CoreResult<Self> {
559 Ok(Self {
560 allocated: 0,
561 resident: 0,
562 mapped: 0,
563 metadata: 0,
564 retained: 0,
565 })
566 }
567
568 pub fn format(&self) -> String {
569 "Memory profiling not enabled".to_string()
570 }
571}
572
573#[cfg(not(feature = "profiling_memory"))]
574pub struct MemoryProfiler;
575
576#[cfg(not(feature = "profiling_memory"))]
577impl MemoryProfiler {
578 pub fn new() -> Self {
579 Self
580 }
581 pub fn get_stats() -> CoreResult<MemoryStats> {
582 MemoryStats::current()
583 }
584 pub fn print_stats() -> CoreResult<()> {
585 Ok(())
586 }
587}
588
589#[cfg(not(feature = "profiling_memory"))]
590pub fn enable_profiling() -> CoreResult<()> {
591 Ok(())
592}
593
594#[cfg(test)]
599#[cfg(feature = "profiling_memory")]
600mod tests {
601 use super::*;
602
603 #[test]
604 fn test_memory_stats() {
605 let stats = MemoryStats::current();
606 assert!(stats.is_ok());
607
608 if let Ok(s) = stats {
609 println!("{}", s.format());
610 assert!(s.resident > 0, "Resident memory should be > 0");
612 }
613 }
614
615 #[test]
616 fn test_memory_profiler() {
617 let mut profiler = MemoryProfiler::new();
618 assert!(profiler.set_baseline().is_ok());
619
620 let _vec: Vec<u8> = vec![0; 1_000_000];
622
623 let delta = profiler.get_delta();
624 assert!(delta.is_ok());
625 }
626
627 #[test]
628 fn test_allocation_tracker() {
629 let mut tracker = AllocationTracker::new();
630
631 assert!(tracker.snapshot("baseline").is_ok());
632
633 let _vec: Vec<u8> = vec![0; 1_000_000];
635
636 assert!(tracker.snapshot("after_alloc").is_ok());
637
638 let analysis = tracker.analyze();
639 assert_eq!(analysis.total_snapshots, 2);
640 }
641
642 #[test]
643 fn test_memory_delta() {
644 let delta = MemoryDelta {
645 allocated_delta: 1_048_576,
646 resident_delta: 2_097_152,
647 mapped_delta: 0,
648 metadata_delta: 0,
649 retained_delta: 0,
650 };
651
652 let formatted = delta.format();
653 assert!(formatted.contains("Allocated"));
654 }
655
656 #[test]
657 fn test_enable_disable_profiling() {
658 assert!(enable_profiling().is_ok());
659 assert!(disable_profiling().is_ok());
660 }
661
662 #[test]
663 fn test_manual_tracking() {
664 TRACKED_ALLOCATED.store(0, Ordering::Relaxed);
666 TRACKED_PEAK.store(0, Ordering::Relaxed);
667
668 MemoryProfiler::track_allocation(1024);
669 assert_eq!(get_tracked_allocated(), 1024);
670
671 MemoryProfiler::track_allocation(2048);
672 assert_eq!(get_tracked_allocated(), 3072);
673
674 MemoryProfiler::track_deallocation(1024);
675 assert_eq!(get_tracked_allocated(), 2048);
676
677 assert_eq!(TRACKED_PEAK.load(Ordering::Relaxed), 3072);
679 }
680
681 #[test]
682 fn test_overhead_and_utilization_ratios() {
683 let stats = MemoryStats {
684 allocated: 1_000_000,
685 resident: 2_000_000,
686 mapped: 4_000_000,
687 metadata: 20_000,
688 retained: 1_000_000,
689 };
690 let overhead = stats.overhead_ratio();
691 assert!((overhead - 0.02).abs() < 1e-6);
692
693 let utilization = stats.utilization_ratio();
694 assert!((utilization - 0.5).abs() < 1e-6);
695 }
696
697 #[test]
698 fn test_zero_stats_ratios() {
699 let stats = MemoryStats {
700 allocated: 0,
701 resident: 0,
702 mapped: 0,
703 metadata: 0,
704 retained: 0,
705 };
706 assert_eq!(stats.overhead_ratio(), 0.0);
707 assert_eq!(stats.utilization_ratio(), 0.0);
708 }
709}