Skip to main content

memscope_rs/capture/platform/
memory_info.rs

1use std::time::{Duration, Instant};
2
3/// Platform-specific memory information collector
4pub struct PlatformMemoryInfo {
5    /// Last collected statistics
6    last_stats: Option<MemoryStats>,
7    /// Collection interval
8    collection_interval: Duration,
9    /// Last collection time
10    last_collection: Option<Instant>,
11    /// Platform-specific context
12    platform_context: MemoryContext,
13}
14
15/// Comprehensive memory statistics
16#[derive(Debug, Clone)]
17pub struct MemoryStats {
18    /// Virtual memory statistics
19    pub virtual_memory: VirtualMemoryStats,
20    /// Physical memory statistics
21    pub physical_memory: PhysicalMemoryStats,
22    /// Process-specific memory statistics
23    pub process_memory: ProcessMemoryStats,
24    /// System-wide memory statistics
25    pub system_memory: SystemMemoryStats,
26    /// Memory pressure indicators
27    pub pressure_indicators: PressureIndicators,
28    /// Collection timestamp
29    pub timestamp: Instant,
30}
31
32impl Default for MemoryStats {
33    fn default() -> Self {
34        MemoryStats {
35            virtual_memory: VirtualMemoryStats::default(),
36            physical_memory: PhysicalMemoryStats::default(),
37            process_memory: ProcessMemoryStats::default(),
38            system_memory: SystemMemoryStats::default(),
39            pressure_indicators: PressureIndicators::default(),
40            timestamp: Instant::now(),
41        }
42    }
43}
44
45/// Virtual memory statistics
46#[derive(Debug, Clone, Default)]
47pub struct VirtualMemoryStats {
48    /// Total virtual address space
49    pub total_virtual: u64,
50    /// Available virtual address space
51    pub available_virtual: u64,
52    /// Used virtual address space
53    pub used_virtual: u64,
54    /// Reserved but uncommitted memory
55    pub reserved: u64,
56    /// Committed memory
57    pub committed: u64,
58}
59
60/// Physical memory statistics
61#[derive(Debug, Clone, Default)]
62pub struct PhysicalMemoryStats {
63    /// Total physical memory (RAM)
64    pub total_physical: u64,
65    /// Available physical memory
66    pub available_physical: u64,
67    /// Used physical memory
68    pub used_physical: u64,
69    /// Memory used by OS cache
70    pub cached: u64,
71    /// Memory used by OS buffers
72    pub buffers: u64,
73    /// Swap/page file statistics
74    pub swap: SwapStats,
75}
76
77/// Swap/page file statistics
78#[derive(Debug, Clone)]
79pub struct SwapStats {
80    /// Total swap/page file size
81    pub total_swap: u64,
82    /// Used swap/page file
83    pub used_swap: u64,
84    /// Available swap/page file
85    pub available_swap: u64,
86    /// Swap-in rate (pages per second)
87    pub swap_in_rate: f64,
88    /// Swap-out rate (pages per second)
89    pub swap_out_rate: f64,
90}
91
92impl Default for SwapStats {
93    fn default() -> Self {
94        SwapStats {
95            total_swap: 0,
96            used_swap: 0,
97            available_swap: 0,
98            swap_in_rate: 0.0,
99            swap_out_rate: 0.0,
100        }
101    }
102}
103
104/// Process-specific memory statistics
105#[derive(Debug, Clone, Default)]
106pub struct ProcessMemoryStats {
107    /// Process virtual memory size
108    pub virtual_size: u64,
109    /// Process resident set size (RSS)
110    pub resident_size: u64,
111    /// Process shared memory
112    pub shared_size: u64,
113    /// Process private memory
114    pub private_size: u64,
115    /// Heap memory usage
116    pub heap_size: u64,
117    /// Stack memory usage
118    pub stack_size: u64,
119    /// Memory-mapped files
120    pub mapped_files: u64,
121    /// Process memory peak usage
122    pub peak_usage: u64,
123}
124
125/// System-wide memory statistics
126#[derive(Debug, Clone)]
127pub struct SystemMemoryStats {
128    /// Number of memory allocations
129    pub allocation_count: u64,
130    /// Number of memory deallocations
131    pub deallocation_count: u64,
132    /// Current active allocations
133    pub active_allocations: u64,
134    /// Total bytes allocated
135    pub total_allocated: u64,
136    /// Total bytes deallocated
137    pub total_deallocated: u64,
138    /// Memory fragmentation level
139    pub fragmentation_level: f64,
140    /// Large page usage
141    pub large_pages: LargePageStats,
142}
143
144impl Default for SystemMemoryStats {
145    fn default() -> Self {
146        SystemMemoryStats {
147            allocation_count: 0,
148            deallocation_count: 0,
149            active_allocations: 0,
150            total_allocated: 0,
151            total_deallocated: 0,
152            fragmentation_level: 0.0,
153            large_pages: LargePageStats::default(),
154        }
155    }
156}
157
158/// Large page usage statistics
159#[derive(Debug, Clone, Default)]
160pub struct LargePageStats {
161    /// Whether large pages are supported
162    pub supported: bool,
163    /// Total large page memory
164    pub total_large_pages: u64,
165    /// Used large page memory
166    pub used_large_pages: u64,
167    /// Large page size
168    pub page_size: u64,
169}
170
171/// Memory pressure indicators
172#[derive(Debug, Clone)]
173pub struct PressureIndicators {
174    /// Overall memory pressure level
175    pub pressure_level: PressureLevel,
176    /// Whether system is in low memory condition
177    pub low_memory: bool,
178    /// Whether swapping is occurring
179    pub swapping_active: bool,
180    /// Memory allocation failure rate
181    pub allocation_failure_rate: f64,
182    /// GC pressure (if applicable)
183    pub gc_pressure: Option<f64>,
184}
185
186impl Default for PressureIndicators {
187    fn default() -> Self {
188        PressureIndicators {
189            pressure_level: PressureLevel::default(),
190            low_memory: false,
191            swapping_active: false,
192            allocation_failure_rate: 0.0,
193            gc_pressure: None,
194        }
195    }
196}
197
198/// Memory pressure levels
199#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
200pub enum PressureLevel {
201    /// Normal memory pressure
202    #[default]
203    Normal,
204    /// Moderate memory pressure
205    Moderate,
206    /// High memory pressure
207    High,
208    /// Critical memory pressure
209    Critical,
210}
211
212/// System information
213#[derive(Debug, Clone)]
214pub struct SystemInfo {
215    /// Operating system name
216    pub os_name: String,
217    /// OS version
218    pub os_version: String,
219    /// System architecture
220    pub architecture: String,
221    /// Number of CPU cores
222    pub cpu_cores: u32,
223    /// CPU cache sizes
224    pub cpu_cache: CpuCacheInfo,
225    /// Page size
226    pub page_size: u64,
227    /// Large page size if supported
228    pub large_page_size: Option<u64>,
229    /// Memory management unit info
230    pub mmu_info: MmuInfo,
231}
232
233/// CPU cache information
234#[derive(Debug, Clone)]
235pub struct CpuCacheInfo {
236    /// L1 cache size per core
237    pub l1_cache_size: u64,
238    /// L2 cache size per core
239    pub l2_cache_size: u64,
240    /// L3 cache size (shared)
241    pub l3_cache_size: Option<u64>,
242    /// Cache line size
243    pub cache_line_size: u64,
244}
245
246/// Memory Management Unit information
247#[derive(Debug, Clone)]
248pub struct MmuInfo {
249    /// Virtual address space size (bits)
250    pub virtual_address_bits: u32,
251    /// Physical address space size (bits)
252    pub physical_address_bits: u32,
253    /// Whether ASLR is enabled
254    pub aslr_enabled: bool,
255    /// Whether NX/XD bit is supported
256    pub nx_bit_supported: bool,
257}
258
259/// Platform-specific context
260#[derive(Debug)]
261struct MemoryContext {
262    /// Whether collector is initialized
263    initialized: bool,
264
265    #[cfg(target_os = "linux")]
266    linux_context: LinuxMemoryContext,
267
268    #[cfg(target_os = "windows")]
269    windows_context: WindowsMemoryContext,
270
271    #[cfg(target_os = "macos")]
272    macos_context: MacOSMemoryContext,
273}
274
275#[cfg(target_os = "linux")]
276#[derive(Debug)]
277struct LinuxMemoryContext {
278    /// Whether /proc/meminfo is accessible
279    proc_meminfo_available: bool,
280    /// Whether /proc/self/status is accessible
281    proc_status_available: bool,
282    /// Whether /proc/self/maps is accessible
283    proc_maps_available: bool,
284}
285
286#[cfg(target_os = "windows")]
287#[derive(Debug)]
288struct WindowsMemoryContext {
289    /// Whether GlobalMemoryStatusEx is available
290    global_memory_api_available: bool,
291    /// Whether GetProcessMemoryInfo is available
292    process_memory_api_available: bool,
293    /// Whether VirtualQueryEx is available
294    virtual_query_available: bool,
295}
296
297#[cfg(target_os = "macos")]
298#[derive(Debug)]
299struct MacOSMemoryContext {
300    /// Whether vm_stat is available
301    vm_stat_available: bool,
302    /// Whether task_info is available
303    task_info_available: bool,
304    /// Whether mach APIs are available
305    mach_api_available: bool,
306}
307
308impl PlatformMemoryInfo {
309    /// Create new memory info collector
310    pub fn new() -> Self {
311        Self {
312            last_stats: None,
313            collection_interval: Duration::from_secs(1),
314            last_collection: None,
315            platform_context: MemoryContext::new(),
316        }
317    }
318
319    /// Initialize memory info collector
320    pub fn initialize(&mut self) -> Result<(), MemoryError> {
321        #[cfg(target_os = "linux")]
322        {
323            self.initialize_linux()
324        }
325
326        #[cfg(target_os = "windows")]
327        {
328            self.initialize_windows()
329        }
330
331        #[cfg(target_os = "macos")]
332        {
333            self.initialize_macos()
334        }
335
336        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
337        {
338            Err(MemoryError::UnsupportedPlatform)
339        }
340    }
341
342    /// Collect current memory statistics
343    pub fn collect_stats(&mut self) -> Result<MemoryStats, MemoryError> {
344        if !self.platform_context.initialized {
345            return Err(MemoryError::NotInitialized);
346        }
347
348        let now = Instant::now();
349
350        // Check if we should collect (rate limiting)
351        if let Some(last) = self.last_collection {
352            if now.duration_since(last) < self.collection_interval {
353                if let Some(ref stats) = self.last_stats {
354                    return Ok(stats.clone());
355                }
356            }
357        }
358
359        let stats = self.perform_collection()?;
360        self.last_stats = Some(stats.clone());
361        self.last_collection = Some(now);
362
363        Ok(stats)
364    }
365
366    /// Get system information
367    pub fn get_system_info(&self) -> Result<SystemInfo, MemoryError> {
368        if !self.platform_context.initialized {
369            return Err(MemoryError::NotInitialized);
370        }
371
372        #[cfg(target_os = "linux")]
373        return self.get_linux_system_info();
374
375        #[cfg(target_os = "windows")]
376        return self.get_windows_system_info();
377
378        #[cfg(target_os = "macos")]
379        return self.get_macos_system_info();
380
381        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
382        Err(MemoryError::UnsupportedPlatform)
383    }
384
385    /// Set collection interval
386    pub fn set_collection_interval(&mut self, interval: Duration) {
387        self.collection_interval = interval;
388    }
389
390    /// Get last collected statistics
391    pub fn get_last_stats(&self) -> Option<&MemoryStats> {
392        self.last_stats.as_ref()
393    }
394
395    fn perform_collection(&self) -> Result<MemoryStats, MemoryError> {
396        #[cfg(target_os = "linux")]
397        return self.collect_linux_stats();
398
399        #[cfg(target_os = "windows")]
400        return self.collect_windows_stats();
401
402        #[cfg(target_os = "macos")]
403        return self.collect_macos_stats();
404
405        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
406        Err(MemoryError::UnsupportedPlatform)
407    }
408
409    #[cfg(target_os = "linux")]
410    fn initialize_linux(&mut self) -> Result<(), MemoryError> {
411        // Check availability of Linux memory information sources
412        self.platform_context.linux_context.proc_meminfo_available =
413            std::path::Path::new("/proc/meminfo").exists();
414        self.platform_context.linux_context.proc_status_available =
415            std::path::Path::new("/proc/self/status").exists();
416        self.platform_context.linux_context.proc_maps_available =
417            std::path::Path::new("/proc/self/maps").exists();
418
419        self.platform_context.initialized = true;
420        Ok(())
421    }
422
423    #[cfg(target_os = "windows")]
424    fn initialize_windows(&mut self) -> Result<(), MemoryError> {
425        // Check availability of Windows memory APIs
426        self.platform_context
427            .windows_context
428            .global_memory_api_available = true; // Simplified
429        self.platform_context
430            .windows_context
431            .process_memory_api_available = true; // Simplified
432        self.platform_context
433            .windows_context
434            .virtual_query_available = true; // Simplified
435
436        self.platform_context.initialized = true;
437        Ok(())
438    }
439
440    #[cfg(target_os = "macos")]
441    fn initialize_macos(&mut self) -> Result<(), MemoryError> {
442        // Check availability of macOS memory APIs
443        self.platform_context.macos_context.vm_stat_available = true; // Simplified
444        self.platform_context.macos_context.task_info_available = true; // Simplified
445        self.platform_context.macos_context.mach_api_available = true; // Simplified
446
447        self.platform_context.initialized = true;
448        Ok(())
449    }
450
451    #[cfg(target_os = "linux")]
452    fn collect_linux_stats(&self) -> Result<MemoryStats, MemoryError> {
453        let mut stats = MemoryStats::default();
454
455        if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
456            for line in meminfo.lines() {
457                let parts: Vec<&str> = line.split_whitespace().collect();
458                if parts.len() < 2 {
459                    continue;
460                }
461                let value_kb: u64 = match parts[1].parse() {
462                    Ok(v) => v,
463                    Err(e) => {
464                        tracing::warn!(
465                            "Failed to parse memory value for '{}': '{}', error: {}",
466                            parts[0],
467                            parts[1],
468                            e
469                        );
470                        0
471                    }
472                };
473                let value_bytes = value_kb * 1024;
474
475                match parts[0] {
476                    "MemTotal:" => stats.physical_memory.total_physical = value_bytes,
477                    "MemAvailable:" => stats.physical_memory.available_physical = value_bytes,
478                    "Buffers:" => stats.physical_memory.buffers = value_bytes,
479                    "Cached:" => stats.physical_memory.cached = value_bytes,
480                    "SwapTotal:" => stats.physical_memory.swap.total_swap = value_bytes,
481                    "SwapFree:" => stats.physical_memory.swap.available_swap = value_bytes,
482                    "SwapUsed:" => stats.physical_memory.swap.used_swap = value_bytes,
483                    "Committed_AS:" => stats.virtual_memory.committed = value_bytes,
484                    "VmallocTotal:" => stats.virtual_memory.total_virtual = value_bytes,
485                    _ => {}
486                }
487            }
488            stats.physical_memory.used_physical = stats
489                .physical_memory
490                .total_physical
491                .saturating_sub(stats.physical_memory.available_physical);
492            stats.physical_memory.swap.used_swap = stats
493                .physical_memory
494                .swap
495                .total_swap
496                .saturating_sub(stats.physical_memory.swap.available_swap);
497            stats.virtual_memory.used_virtual = stats.virtual_memory.committed;
498            stats.virtual_memory.available_virtual = stats
499                .virtual_memory
500                .total_virtual
501                .saturating_sub(stats.virtual_memory.used_virtual);
502            // Note: Real reserved memory would require reading /proc/iomem
503            // Setting to 0 as fallback since it's not currently implemented
504            stats.virtual_memory.reserved = 0;
505        }
506
507        if let Ok(status) = std::fs::read_to_string("/proc/self/status") {
508            for line in status.lines() {
509                let parts: Vec<&str> = line.split_whitespace().collect();
510                if parts.len() < 2 {
511                    continue;
512                }
513                let value_kb: u64 = parts[1].parse().unwrap_or(0);
514                let value_bytes = value_kb * 1024;
515
516                match parts[0] {
517                    "VmSize:" => stats.process_memory.virtual_size = value_bytes,
518                    "VmRSS:" => stats.process_memory.resident_size = value_bytes,
519                    "RssAnon:" => stats.process_memory.private_size = value_bytes,
520                    "RssFile:" => stats.process_memory.mapped_files = value_bytes,
521                    "VmData:" => stats.process_memory.heap_size = value_bytes,
522                    "VmStk:" => stats.process_memory.stack_size = value_bytes,
523                    "VmPeak:" => stats.process_memory.peak_usage = value_bytes,
524                    _ => {}
525                }
526            }
527        }
528
529        stats.pressure_indicators = PressureIndicators::default();
530
531        Ok(stats)
532    }
533
534    #[cfg(target_os = "windows")]
535    fn collect_windows_stats(&self) -> Result<MemoryStats, MemoryError> {
536        use windows_sys::Win32::System::SystemInformation::{
537            GetSystemInfo, GlobalMemoryStatusEx, MEMORYSTATUSEX, SYSTEM_INFO,
538        };
539
540        let mut mem_status: MEMORYSTATUSEX = unsafe { std::mem::zeroed() };
541        mem_status.dwLength = std::mem::size_of::<MEMORYSTATUSEX>() as u32;
542
543        unsafe {
544            if GlobalMemoryStatusEx(&mut mem_status) == 0 {
545                return Err(MemoryError::SystemError(
546                    "Failed to get memory status".to_string(),
547                ));
548            }
549        }
550
551        let mut sys_info: SYSTEM_INFO = unsafe { std::mem::zeroed() };
552        unsafe { GetSystemInfo(&mut sys_info) };
553
554        let total_physical = mem_status.ullTotalPhys;
555        let available_physical = mem_status.ullAvailPhys;
556        let total_virtual = mem_status.ullTotalVirtual;
557        let available_virtual = mem_status.ullAvailVirtual;
558
559        let _page_size = sys_info.dwPageSize as u64;
560        let _total_memory_bytes = total_physical;
561        let _available_memory_bytes = available_physical;
562        let used_memory_bytes = total_physical.saturating_sub(available_physical);
563        let _memory_usage_percent = if total_physical > 0 {
564            (used_memory_bytes as f64 / total_physical as f64 * 100.0).round() as u32
565        } else {
566            0
567        };
568
569        Ok(MemoryStats {
570            virtual_memory: VirtualMemoryStats {
571                total_virtual,
572                available_virtual,
573                used_virtual: total_virtual - available_virtual,
574                reserved: total_virtual / 4,
575                // Use total page file as an estimate for committed memory
576                committed: mem_status.ullTotalPageFile,
577            },
578            physical_memory: PhysicalMemoryStats {
579                total_physical,
580                available_physical,
581                used_physical: total_physical - available_physical,
582                cached: 0,
583                buffers: 0,
584                swap: SwapStats {
585                    total_swap: mem_status.ullTotalPageFile,
586                    used_swap: mem_status.ullTotalPageFile - mem_status.ullAvailPageFile,
587                    available_swap: mem_status.ullAvailPageFile,
588                    swap_in_rate: 0.0,
589                    swap_out_rate: 0.0,
590                },
591            },
592            process_memory: ProcessMemoryStats {
593                virtual_size: 0,
594                resident_size: 0,
595                shared_size: 0,
596                private_size: 0,
597                heap_size: 0,
598                stack_size: 0,
599                mapped_files: 0,
600                peak_usage: 0,
601            },
602            system_memory: SystemMemoryStats {
603                allocation_count: 0,
604                deallocation_count: 0,
605                active_allocations: 0,
606                total_allocated: 0,
607                total_deallocated: 0,
608                fragmentation_level: 0.0,
609                large_pages: LargePageStats {
610                    supported: true,
611                    total_large_pages: 0,
612                    used_large_pages: 0,
613                    page_size: sys_info.dwPageSize as u64,
614                },
615            },
616            pressure_indicators: PressureIndicators {
617                pressure_level: if mem_status.dwMemoryLoad > 90 {
618                    PressureLevel::Critical
619                } else if mem_status.dwMemoryLoad > 70 {
620                    PressureLevel::High
621                } else if mem_status.dwMemoryLoad > 50 {
622                    PressureLevel::Moderate
623                } else {
624                    PressureLevel::Normal
625                },
626                low_memory: mem_status.dwMemoryLoad > 80,
627                swapping_active: mem_status.ullTotalPageFile - mem_status.ullAvailPageFile > 0,
628                allocation_failure_rate: 0.0,
629                gc_pressure: None,
630            },
631            timestamp: Instant::now(),
632        })
633    }
634
635    #[cfg(target_os = "macos")]
636    #[allow(deprecated)] // libc::mach_host_self and mach_task_self are deprecated in favor of mach2 crate
637    fn collect_macos_stats(&self) -> Result<MemoryStats, MemoryError> {
638        use libc::{c_int, host_statistics64, mach_host_self, vm_statistics64};
639
640        // Get host port
641        let host = unsafe { mach_host_self() };
642
643        // Get VM statistics
644        let mut vm_stats: vm_statistics64 = unsafe { std::mem::zeroed() };
645        let mut count =
646            (std::mem::size_of::<vm_statistics64>() / std::mem::size_of::<c_int>()) as u32;
647
648        let result = unsafe {
649            host_statistics64(
650                host,
651                libc::HOST_VM_INFO64,
652                &mut vm_stats as *mut vm_statistics64 as *mut c_int,
653                &mut count,
654            )
655        };
656
657        // Get physical memory
658        let mut total_physical: u64 = 0;
659        unsafe {
660            let mut size = std::mem::size_of::<u64>();
661            if libc::sysctlbyname(
662                c"hw.memsize".as_ptr(),
663                &mut total_physical as *mut u64 as *mut libc::c_void,
664                &mut size,
665                std::ptr::null_mut(),
666                0,
667            ) != 0
668            {
669                // Failed to get physical memory size
670                return Err(MemoryError::SystemError(
671                    "Failed to get physical memory size via sysctl(hw.memsize)".to_string(),
672                ));
673            }
674        }
675
676        // Get page size
677        let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as u64 };
678        let page_size = if page_size == 0 { 4096 } else { page_size };
679
680        // Calculate memory values from VM statistics
681        let (physical_memory, available_physical, used_physical, cached, buffers) = if result == 0 {
682            let free = vm_stats.free_count as u64 * page_size;
683            let inactive = vm_stats.inactive_count as u64 * page_size;
684            let wired = vm_stats.wire_count as u64 * page_size;
685            let active = vm_stats.active_count as u64 * page_size;
686            let speculative = vm_stats.speculative_count as u64 * page_size;
687
688            let used = wired + active;
689            let available = free + inactive + speculative;
690            let cached_pages = inactive; // On macOS, inactive pages are similar to cache
691
692            (total_physical, available, used, cached_pages, 0)
693        } else {
694            // Fallback values if host_statistics64 fails
695            (total_physical, total_physical / 2, total_physical / 2, 0, 0)
696        };
697
698        // Get swap info - estimate from compressed memory
699        let compressed = vm_stats.compressor_page_count as u64 * page_size;
700        let swap_used_estimated = compressed; // Compressed pages often correlate with swap
701
702        // Get real swap info using sysctl
703        let (total_swap, available_swap) = unsafe {
704            let mut swap_usage: libc::xsw_usage = std::mem::zeroed();
705            let mut size = std::mem::size_of::<libc::xsw_usage>();
706            let result = libc::sysctlbyname(
707                c"vm.swapusage".as_ptr(),
708                &mut swap_usage as *mut libc::xsw_usage as *mut libc::c_void,
709                &mut size,
710                std::ptr::null_mut(),
711                0,
712            );
713
714            if result == 0 {
715                (swap_usage.xsu_total, swap_usage.xsu_avail)
716            } else {
717                // Fallback: use compressed memory as estimate
718                (compressed, 0)
719            }
720        };
721
722        // Get process memory info using task_info
723        let process_memory = unsafe {
724            let mut task_info: libc::mach_task_basic_info = std::mem::zeroed();
725            let mut count = (std::mem::size_of::<libc::mach_task_basic_info>()
726                / std::mem::size_of::<libc::natural_t>()) as u32;
727
728            let result = libc::task_info(
729                libc::mach_task_self(),
730                libc::MACH_TASK_BASIC_INFO,
731                &mut task_info as *mut libc::mach_task_basic_info as *mut libc::c_int,
732                &mut count,
733            );
734
735            if result == 0 {
736                ProcessMemoryStats {
737                    virtual_size: task_info.virtual_size,
738                    resident_size: task_info.resident_size,
739                    shared_size: 0,                        // Not directly available
740                    private_size: task_info.resident_size, // Approximation
741                    heap_size: 0,                          // Not directly available
742                    stack_size: 0,                         // Not directly available
743                    mapped_files: 0,
744                    peak_usage: task_info.resident_size_max,
745                }
746            } else {
747                // Fallback - return zero values when task_info fails
748                ProcessMemoryStats {
749                    virtual_size: 0,
750                    resident_size: 0,
751                    shared_size: 0,
752                    private_size: 0,
753                    heap_size: 0,
754                    stack_size: 0,
755                    mapped_files: 0,
756                    peak_usage: 0,
757                }
758            }
759        };
760
761        // Determine memory pressure
762        let pressure_level = if available_physical < total_physical / 10 {
763            PressureLevel::Critical
764        } else if available_physical < total_physical / 5 {
765            PressureLevel::High
766        } else if available_physical < total_physical / 3 {
767            PressureLevel::Moderate
768        } else {
769            PressureLevel::Normal
770        };
771
772        Ok(MemoryStats {
773            virtual_memory: VirtualMemoryStats {
774                // On macOS, user space virtual memory is limited by the architecture
775                // For x86_64 and ARM64, user space typically has 128TB virtual address space
776                // We use a more accurate estimate based on the process's virtual memory
777                total_virtual: process_memory.virtual_size.max(physical_memory * 2),
778                available_virtual: physical_memory,
779                used_virtual: process_memory.virtual_size,
780                reserved: process_memory.virtual_size / 4,
781                committed: process_memory.virtual_size / 4,
782            },
783            physical_memory: PhysicalMemoryStats {
784                total_physical: physical_memory,
785                available_physical,
786                used_physical,
787                cached,
788                buffers,
789                swap: SwapStats {
790                    total_swap,
791                    used_swap: swap_used_estimated,
792                    available_swap,
793                    swap_in_rate: 0.0,
794                    swap_out_rate: 0.0,
795                },
796            },
797            process_memory,
798            system_memory: SystemMemoryStats {
799                allocation_count: 0,
800                deallocation_count: 0,
801                active_allocations: 0,
802                total_allocated: 0,
803                total_deallocated: 0,
804                fragmentation_level: 0.0,
805                large_pages: LargePageStats {
806                    supported: false,
807                    total_large_pages: 0,
808                    used_large_pages: 0,
809                    page_size,
810                },
811            },
812            pressure_indicators: PressureIndicators {
813                pressure_level,
814                low_memory: pressure_level >= PressureLevel::High,
815                swapping_active: swap_used_estimated > 0,
816                allocation_failure_rate: 0.0,
817                gc_pressure: None,
818            },
819            timestamp: Instant::now(),
820        })
821    }
822
823    #[cfg(target_os = "linux")]
824    fn get_linux_system_info(&self) -> Result<SystemInfo, MemoryError> {
825        // Get OS version from /proc/sys/kernel/osrelease
826        let os_version = std::fs::read_to_string("/proc/sys/kernel/osrelease")
827            .map(|s| s.trim().to_string())
828            .unwrap_or_else(|_| "Unknown".to_string());
829
830        // Get architecture from uname
831        let architecture = unsafe {
832            let mut uname: libc::utsname = std::mem::zeroed();
833            if libc::uname(&mut uname) == 0 {
834                let machine = std::ffi::CStr::from_ptr(uname.machine.as_ptr())
835                    .to_string_lossy()
836                    .to_string();
837                machine
838            } else {
839                "unknown".to_string()
840            }
841        };
842
843        // Get CPU cores from /proc/cpuinfo
844        let cpu_cores = if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") {
845            cpuinfo
846                .lines()
847                .filter(|line| line.starts_with("processor"))
848                .count() as u32
849        } else {
850            1
851        };
852
853        // Get page size
854        let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as u64 };
855        let page_size = if page_size == 0 { 4096 } else { page_size };
856
857        // Get cache info from /proc/cpuinfo
858        let (l1_cache_size, l2_cache_size, l3_cache_size, cache_line_size) =
859            if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") {
860                let mut l1 = 0u64;
861                let mut l2 = 0u64;
862                let mut l3 = 0u64;
863                let mut line_size = 64u64;
864
865                for line in cpuinfo.lines() {
866                    if line.contains("cache size") {
867                        // Format: "cache size : 6144 KB"
868                        if let Some(kb_str) = line.split(':').nth(1) {
869                            if let Some(kb_val) = kb_str.split_whitespace().next() {
870                                if let Ok(kb) = kb_val.parse::<u64>() {
871                                    let bytes = kb * 1024;
872                                    // Heuristic: L1 < 256KB, L2 < 4MB, L3 >= 4MB
873                                    if bytes < 256 * 1024 && l1 == 0 {
874                                        l1 = bytes;
875                                    } else if bytes < 4 * 1024 * 1024 && l2 == 0 {
876                                        l2 = bytes;
877                                    } else if bytes >= 4 * 1024 * 1024 && l3 == 0 {
878                                        l3 = bytes;
879                                    }
880                                }
881                            }
882                        }
883                    }
884                    if line.contains("cache_alignment") {
885                        // Format: "cache_alignment : 64"
886                        if let Some(val_str) = line.split(':').nth(1) {
887                            if let Ok(val) = val_str.trim().parse::<u64>() {
888                                line_size = val;
889                            }
890                        }
891                    }
892                }
893
894                (l1, l2, l3, line_size)
895            } else {
896                (0, 0, 0, 64)
897            };
898
899        Ok(SystemInfo {
900            os_name: "Linux".to_string(),
901            os_version,
902            architecture,
903            cpu_cores,
904            cpu_cache: CpuCacheInfo {
905                l1_cache_size,
906                l2_cache_size,
907                l3_cache_size: if l3_cache_size > 0 {
908                    Some(l3_cache_size)
909                } else {
910                    None
911                },
912                cache_line_size,
913            },
914            page_size,
915            large_page_size: None, // Not universally supported on Linux
916            mmu_info: MmuInfo {
917                virtual_address_bits: 48,  // x86_64 typical
918                physical_address_bits: 40, // x86_64 typical
919                aslr_enabled: true,
920                nx_bit_supported: true,
921            },
922        })
923    }
924
925    #[cfg(target_os = "windows")]
926    fn get_windows_system_info(&self) -> Result<SystemInfo, MemoryError> {
927        use windows_sys::Win32::System::SystemInformation::{GetSystemInfo, SYSTEM_INFO};
928
929        let mut sys_info: SYSTEM_INFO = unsafe { std::mem::zeroed() };
930        unsafe { GetSystemInfo(&mut sys_info) };
931
932        let page_size = sys_info.dwPageSize as u64;
933        let cpu_cores = sys_info.dwNumberOfProcessors as u32;
934
935        let architecture = match unsafe { sys_info.Anonymous.Anonymous.wProcessorArchitecture } {
936            5 => "ARM",
937            6 => "ARM64",
938            9 => "x64",
939            12 => "ARM",
940            0 => "x86",
941            _ => "Unknown",
942        };
943
944        Ok(SystemInfo {
945            os_name: "Windows".to_string(),
946            os_version: std::env::var("OS").unwrap_or_else(|_| "Unknown".to_string()),
947            architecture: architecture.to_string(),
948            cpu_cores,
949            cpu_cache: CpuCacheInfo {
950                l1_cache_size: 0,
951                l2_cache_size: 0,
952                l3_cache_size: None,
953                cache_line_size: page_size,
954            },
955            page_size,
956            large_page_size: Some(sys_info.dwPageSize as u64),
957            mmu_info: MmuInfo {
958                virtual_address_bits: if unsafe {
959                    sys_info.Anonymous.Anonymous.wProcessorArchitecture
960                } == 9
961                {
962                    48
963                } else {
964                    32
965                },
966                physical_address_bits: 0,
967                aslr_enabled: true,
968                nx_bit_supported: true,
969            },
970        })
971    }
972
973    #[cfg(target_os = "macos")]
974    fn get_macos_system_info(&self) -> Result<SystemInfo, MemoryError> {
975        // Get OS version
976        let os_version = unsafe {
977            let mut size: libc::size_t = 256;
978            let mut buf = [0u8; 256];
979            if libc::sysctlbyname(
980                c"kern.osrelease".as_ptr(),
981                buf.as_mut_ptr() as *mut libc::c_void,
982                &mut size,
983                std::ptr::null_mut(),
984                0,
985            ) == 0
986                && size > 0
987            {
988                String::from_utf8_lossy(&buf[..size.min(buf.len())]).to_string()
989            } else {
990                "Unknown".to_string()
991            }
992        };
993
994        // Get architecture
995        let architecture = unsafe {
996            let mut size: libc::size_t = 256;
997            let mut buf = [0u8; 256];
998            if libc::sysctlbyname(
999                c"hw.machine".as_ptr(),
1000                buf.as_mut_ptr() as *mut libc::c_void,
1001                &mut size,
1002                std::ptr::null_mut(),
1003                0,
1004            ) == 0
1005                && size > 0
1006            {
1007                let arch_str = String::from_utf8_lossy(&buf[..size.min(buf.len())]).to_string();
1008                // Convert arm64, x86_64 to standard format
1009                if arch_str.contains("arm64") || arch_str.contains("arm") {
1010                    "arm64".to_string()
1011                } else {
1012                    arch_str
1013                }
1014            } else {
1015                "unknown".to_string()
1016            }
1017        };
1018
1019        // Get CPU cores
1020        let mut size = std::mem::size_of::<u32>();
1021        let mut cpu_cores: u32 = 1;
1022        unsafe {
1023            let mut mib: [libc::c_int; 2] = [libc::CTL_HW, libc::HW_NCPU];
1024            if libc::sysctl(
1025                mib.as_mut_ptr(),
1026                mib.len() as libc::c_uint,
1027                &mut cpu_cores as *mut u32 as *mut libc::c_void,
1028                &mut size,
1029                std::ptr::null_mut(),
1030                0,
1031            ) == 0
1032            {
1033                // Successfully got CPU cores
1034            }
1035        }
1036
1037        // Get page size
1038        let mut page_size: u64 = 4096;
1039        unsafe {
1040            size = std::mem::size_of::<u64>();
1041            if libc::sysctlbyname(
1042                c"hw.pagesize".as_ptr(),
1043                &mut page_size as *mut u64 as *mut libc::c_void,
1044                &mut size,
1045                std::ptr::null_mut(),
1046                0,
1047            ) != 0
1048            {
1049                page_size = 4096; // Default fallback
1050            }
1051        }
1052
1053        // Get cache line size
1054        let mut cache_line_size: u64 = 64;
1055        unsafe {
1056            size = std::mem::size_of::<u64>();
1057            if libc::sysctlbyname(
1058                c"hw.cachelinesize".as_ptr(),
1059                &mut cache_line_size as *mut u64 as *mut libc::c_void,
1060                &mut size,
1061                std::ptr::null_mut(),
1062                0,
1063            ) != 0
1064            {
1065                cache_line_size = 64; // Default fallback
1066            }
1067        }
1068
1069        // Get L1 cache size
1070        let mut l1_cache_size: u64 = 0;
1071        unsafe {
1072            size = std::mem::size_of::<u64>();
1073            if libc::sysctlbyname(
1074                c"hw.l1dcachesize".as_ptr(),
1075                &mut l1_cache_size as *mut u64 as *mut libc::c_void,
1076                &mut size,
1077                std::ptr::null_mut(),
1078                0,
1079            ) != 0
1080            {
1081                // Try alternative
1082                if libc::sysctlbyname(
1083                    c"hw.l1icachesize".as_ptr(),
1084                    &mut l1_cache_size as *mut u64 as *mut libc::c_void,
1085                    &mut size,
1086                    std::ptr::null_mut(),
1087                    0,
1088                ) != 0
1089                {
1090                    l1_cache_size = 0;
1091                }
1092            }
1093        }
1094
1095        // Get L2 cache size
1096        let mut l2_cache_size: u64 = 0;
1097        unsafe {
1098            size = std::mem::size_of::<u64>();
1099            if libc::sysctlbyname(
1100                c"hw.l2cachesize".as_ptr(),
1101                &mut l2_cache_size as *mut u64 as *mut libc::c_void,
1102                &mut size,
1103                std::ptr::null_mut(),
1104                0,
1105            ) != 0
1106            {
1107                l2_cache_size = 0;
1108            }
1109        }
1110
1111        // Get L3 cache size (may not exist on Apple Silicon)
1112        let mut l3_cache_size: u64 = 0;
1113        unsafe {
1114            size = std::mem::size_of::<u64>();
1115            if libc::sysctlbyname(
1116                c"hw.l3cachesize".as_ptr(),
1117                &mut l3_cache_size as *mut u64 as *mut libc::c_void,
1118                &mut size,
1119                std::ptr::null_mut(),
1120                0,
1121            ) != 0
1122            {
1123                l3_cache_size = 0;
1124            }
1125        }
1126
1127        Ok(SystemInfo {
1128            os_name: "macOS".to_string(),
1129            os_version,
1130            architecture,
1131            cpu_cores,
1132            cpu_cache: CpuCacheInfo {
1133                l1_cache_size,
1134                l2_cache_size,
1135                l3_cache_size: if l3_cache_size > 0 {
1136                    Some(l3_cache_size)
1137                } else {
1138                    None
1139                },
1140                cache_line_size,
1141            },
1142            page_size,
1143            large_page_size: None, // Not supported on Apple Silicon
1144            mmu_info: MmuInfo {
1145                virtual_address_bits: 48,
1146                physical_address_bits: 40,
1147                aslr_enabled: true,
1148                nx_bit_supported: true,
1149            },
1150        })
1151    }
1152}
1153
1154impl MemoryContext {
1155    fn new() -> Self {
1156        Self {
1157            initialized: false,
1158            #[cfg(target_os = "linux")]
1159            linux_context: LinuxMemoryContext {
1160                proc_meminfo_available: false,
1161                proc_status_available: false,
1162                proc_maps_available: false,
1163            },
1164            #[cfg(target_os = "windows")]
1165            windows_context: WindowsMemoryContext {
1166                global_memory_api_available: false,
1167                process_memory_api_available: false,
1168                virtual_query_available: false,
1169            },
1170            #[cfg(target_os = "macos")]
1171            macos_context: MacOSMemoryContext {
1172                vm_stat_available: false,
1173                task_info_available: false,
1174                mach_api_available: false,
1175            },
1176        }
1177    }
1178}
1179
1180/// Errors that can occur during memory information collection
1181#[derive(Debug, Clone, PartialEq)]
1182pub enum MemoryError {
1183    /// Platform not supported
1184    UnsupportedPlatform,
1185    /// Collector not initialized
1186    NotInitialized,
1187    /// Permission denied
1188    PermissionDenied,
1189    /// System API error
1190    SystemError(String),
1191    /// Parse error
1192    ParseError(String),
1193    /// I/O error
1194    IoError(String),
1195    /// Feature not implemented
1196    NotImplemented(String),
1197}
1198
1199impl Default for PlatformMemoryInfo {
1200    fn default() -> Self {
1201        Self::new()
1202    }
1203}
1204
1205impl std::fmt::Display for MemoryError {
1206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1207        match self {
1208            MemoryError::UnsupportedPlatform => {
1209                write!(f, "Platform not supported for memory info collection")
1210            }
1211            MemoryError::NotInitialized => write!(f, "Memory info collector not initialized"),
1212            MemoryError::PermissionDenied => write!(f, "Permission denied for memory info access"),
1213            MemoryError::SystemError(msg) => write!(f, "System error: {}", msg),
1214            MemoryError::ParseError(msg) => write!(f, "Parse error: {}", msg),
1215            MemoryError::IoError(msg) => write!(f, "I/O error: {}", msg),
1216            MemoryError::NotImplemented(msg) => {
1217                write!(f, "Feature not implemented: {}", msg)
1218            }
1219        }
1220    }
1221}
1222
1223impl std::error::Error for MemoryError {}
1224
1225#[cfg(test)]
1226mod tests {
1227    use super::*;
1228
1229    #[test]
1230    fn test_memory_info_creation() {
1231        let info = PlatformMemoryInfo::new();
1232        assert!(!info.platform_context.initialized);
1233        assert!(info.last_stats.is_none());
1234    }
1235
1236    #[test]
1237    fn test_initialization() {
1238        let mut info = PlatformMemoryInfo::new();
1239        let result = info.initialize();
1240
1241        #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
1242        assert!(result.is_ok());
1243
1244        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
1245        assert_eq!(result, Err(MemoryError::UnsupportedPlatform));
1246    }
1247
1248    #[test]
1249    fn test_stats_collection() {
1250        let mut info = PlatformMemoryInfo::new();
1251        let _ = info.initialize();
1252
1253        let result = info.collect_stats();
1254
1255        #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
1256        {
1257            if info.platform_context.initialized {
1258                assert!(result.is_ok());
1259                let stats = result.expect("Stats should be collected");
1260                assert!(stats.physical_memory.total_physical > 0);
1261                assert!(stats.virtual_memory.total_virtual > 0);
1262            }
1263        }
1264    }
1265
1266    #[test]
1267    fn test_system_info() {
1268        let mut info = PlatformMemoryInfo::new();
1269        let _ = info.initialize();
1270
1271        let result = info.get_system_info();
1272
1273        #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
1274        {
1275            if info.platform_context.initialized {
1276                assert!(result.is_ok());
1277                let sys_info = result.expect("System info should be available");
1278                assert!(!sys_info.os_name.is_empty());
1279                assert!(sys_info.cpu_cores > 0);
1280                assert!(sys_info.page_size > 0);
1281            }
1282        }
1283    }
1284
1285    #[test]
1286    fn test_pressure_level_ordering() {
1287        assert!(PressureLevel::Critical > PressureLevel::High);
1288        assert!(PressureLevel::High > PressureLevel::Moderate);
1289        assert!(PressureLevel::Moderate > PressureLevel::Normal);
1290    }
1291
1292    #[test]
1293    fn test_collection_interval() {
1294        let mut info = PlatformMemoryInfo::new();
1295        info.set_collection_interval(Duration::from_millis(500));
1296        assert_eq!(info.collection_interval, Duration::from_millis(500));
1297    }
1298}