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    /// Safely get the current CPU core index of the calling thread.
386    pub fn get_current_cpu(&self) -> Option<u32> {
387        get_current_cpu_impl()
388    }
389
390    /// Standalone implementation shared between the method and external callers.
391    pub fn get_current_cpu_standalone() -> Option<u32> {
392        get_current_cpu_impl()
393    }
394
395    /// Set collection interval
396    pub fn set_collection_interval(&mut self, interval: Duration) {
397        self.collection_interval = interval;
398    }
399
400    /// Get last collected statistics
401    pub fn get_last_stats(&self) -> Option<&MemoryStats> {
402        self.last_stats.as_ref()
403    }
404
405    fn perform_collection(&self) -> Result<MemoryStats, MemoryError> {
406        #[cfg(target_os = "linux")]
407        return self.collect_linux_stats();
408
409        #[cfg(target_os = "windows")]
410        return self.collect_windows_stats();
411
412        #[cfg(target_os = "macos")]
413        return self.collect_macos_stats();
414
415        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
416        Err(MemoryError::UnsupportedPlatform)
417    }
418
419    #[cfg(target_os = "linux")]
420    fn initialize_linux(&mut self) -> Result<(), MemoryError> {
421        // Check availability of Linux memory information sources
422        self.platform_context.linux_context.proc_meminfo_available =
423            std::path::Path::new("/proc/meminfo").exists();
424        self.platform_context.linux_context.proc_status_available =
425            std::path::Path::new("/proc/self/status").exists();
426        self.platform_context.linux_context.proc_maps_available =
427            std::path::Path::new("/proc/self/maps").exists();
428
429        self.platform_context.initialized = true;
430        Ok(())
431    }
432
433    #[cfg(target_os = "windows")]
434    fn initialize_windows(&mut self) -> Result<(), MemoryError> {
435        // Check availability of Windows memory APIs
436        self.platform_context
437            .windows_context
438            .global_memory_api_available = true; // Simplified
439        self.platform_context
440            .windows_context
441            .process_memory_api_available = true; // Simplified
442        self.platform_context
443            .windows_context
444            .virtual_query_available = true; // Simplified
445
446        self.platform_context.initialized = true;
447        Ok(())
448    }
449
450    #[cfg(target_os = "macos")]
451    fn initialize_macos(&mut self) -> Result<(), MemoryError> {
452        // Check availability of macOS memory APIs
453        self.platform_context.macos_context.vm_stat_available = true; // Simplified
454        self.platform_context.macos_context.task_info_available = true; // Simplified
455        self.platform_context.macos_context.mach_api_available = true; // Simplified
456
457        self.platform_context.initialized = true;
458        Ok(())
459    }
460
461    #[cfg(target_os = "linux")]
462    fn collect_linux_stats(&self) -> Result<MemoryStats, MemoryError> {
463        let mut stats = MemoryStats::default();
464
465        if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
466            for line in meminfo.lines() {
467                let parts: Vec<&str> = line.split_whitespace().collect();
468                if parts.len() < 2 {
469                    continue;
470                }
471                let value_kb: u64 = match parts[1].parse() {
472                    Ok(v) => v,
473                    Err(e) => {
474                        tracing::warn!(
475                            "Failed to parse memory value for '{}': '{}', error: {}",
476                            parts[0],
477                            parts[1],
478                            e
479                        );
480                        0
481                    }
482                };
483                let value_bytes = value_kb * 1024;
484
485                match parts[0] {
486                    "MemTotal:" => stats.physical_memory.total_physical = value_bytes,
487                    "MemAvailable:" => stats.physical_memory.available_physical = value_bytes,
488                    "Buffers:" => stats.physical_memory.buffers = value_bytes,
489                    "Cached:" => stats.physical_memory.cached = value_bytes,
490                    "SwapTotal:" => stats.physical_memory.swap.total_swap = value_bytes,
491                    "SwapFree:" => stats.physical_memory.swap.available_swap = value_bytes,
492                    "SwapUsed:" => stats.physical_memory.swap.used_swap = value_bytes,
493                    "Committed_AS:" => stats.virtual_memory.committed = value_bytes,
494                    "VmallocTotal:" => stats.virtual_memory.total_virtual = value_bytes,
495                    _ => {}
496                }
497            }
498            stats.physical_memory.used_physical = stats
499                .physical_memory
500                .total_physical
501                .saturating_sub(stats.physical_memory.available_physical);
502            stats.physical_memory.swap.used_swap = stats
503                .physical_memory
504                .swap
505                .total_swap
506                .saturating_sub(stats.physical_memory.swap.available_swap);
507            stats.virtual_memory.used_virtual = stats.virtual_memory.committed;
508            stats.virtual_memory.available_virtual = stats
509                .virtual_memory
510                .total_virtual
511                .saturating_sub(stats.virtual_memory.used_virtual);
512            // Note: Real reserved memory would require reading /proc/iomem
513            // Setting to 0 as fallback since it's not currently implemented
514            stats.virtual_memory.reserved = 0;
515        }
516
517        if let Ok(status) = std::fs::read_to_string("/proc/self/status") {
518            for line in status.lines() {
519                let parts: Vec<&str> = line.split_whitespace().collect();
520                if parts.len() < 2 {
521                    continue;
522                }
523                let value_kb: u64 = parts[1].parse().unwrap_or(0);
524                let value_bytes = value_kb * 1024;
525
526                match parts[0] {
527                    "VmSize:" => stats.process_memory.virtual_size = value_bytes,
528                    "VmRSS:" => stats.process_memory.resident_size = value_bytes,
529                    "RssAnon:" => stats.process_memory.private_size = value_bytes,
530                    "RssFile:" => stats.process_memory.mapped_files = value_bytes,
531                    "VmData:" => stats.process_memory.heap_size = value_bytes,
532                    "VmStk:" => stats.process_memory.stack_size = value_bytes,
533                    "VmPeak:" => stats.process_memory.peak_usage = value_bytes,
534                    _ => {}
535                }
536            }
537        }
538
539        stats.pressure_indicators = PressureIndicators::default();
540
541        Ok(stats)
542    }
543
544    #[cfg(target_os = "windows")]
545    fn collect_windows_stats(&self) -> Result<MemoryStats, MemoryError> {
546        use windows_sys::Win32::System::SystemInformation::{
547            GetSystemInfo, GlobalMemoryStatusEx, MEMORYSTATUSEX, SYSTEM_INFO,
548        };
549
550        let mut mem_status: MEMORYSTATUSEX = unsafe { std::mem::zeroed() };
551        mem_status.dwLength = std::mem::size_of::<MEMORYSTATUSEX>() as u32;
552
553        unsafe {
554            if GlobalMemoryStatusEx(&mut mem_status) == 0 {
555                return Err(MemoryError::SystemError(
556                    "Failed to get memory status".to_string(),
557                ));
558            }
559        }
560
561        let mut sys_info: SYSTEM_INFO = unsafe { std::mem::zeroed() };
562        unsafe { GetSystemInfo(&mut sys_info) };
563
564        let total_physical = mem_status.ullTotalPhys;
565        let available_physical = mem_status.ullAvailPhys;
566        let total_virtual = mem_status.ullTotalVirtual;
567        let available_virtual = mem_status.ullAvailVirtual;
568
569        let _page_size = sys_info.dwPageSize as u64;
570        let _total_memory_bytes = total_physical;
571        let _available_memory_bytes = available_physical;
572        let used_memory_bytes = total_physical.saturating_sub(available_physical);
573        let _memory_usage_percent = if total_physical > 0 {
574            (used_memory_bytes as f64 / total_physical as f64 * 100.0).round() as u32
575        } else {
576            0
577        };
578
579        Ok(MemoryStats {
580            virtual_memory: VirtualMemoryStats {
581                total_virtual,
582                available_virtual,
583                used_virtual: total_virtual - available_virtual,
584                reserved: total_virtual / 4,
585                // Use total page file as an estimate for committed memory
586                committed: mem_status.ullTotalPageFile,
587            },
588            physical_memory: PhysicalMemoryStats {
589                total_physical,
590                available_physical,
591                used_physical: total_physical - available_physical,
592                cached: 0,
593                buffers: 0,
594                swap: SwapStats {
595                    total_swap: mem_status.ullTotalPageFile,
596                    used_swap: mem_status.ullTotalPageFile - mem_status.ullAvailPageFile,
597                    available_swap: mem_status.ullAvailPageFile,
598                    swap_in_rate: 0.0,
599                    swap_out_rate: 0.0,
600                },
601            },
602            process_memory: ProcessMemoryStats {
603                virtual_size: 0,
604                resident_size: 0,
605                shared_size: 0,
606                private_size: 0,
607                heap_size: 0,
608                stack_size: 0,
609                mapped_files: 0,
610                peak_usage: 0,
611            },
612            system_memory: SystemMemoryStats {
613                allocation_count: 0,
614                deallocation_count: 0,
615                active_allocations: 0,
616                total_allocated: 0,
617                total_deallocated: 0,
618                fragmentation_level: 0.0,
619                large_pages: LargePageStats {
620                    supported: true,
621                    total_large_pages: 0,
622                    used_large_pages: 0,
623                    page_size: sys_info.dwPageSize as u64,
624                },
625            },
626            pressure_indicators: PressureIndicators {
627                pressure_level: if mem_status.dwMemoryLoad > 90 {
628                    PressureLevel::Critical
629                } else if mem_status.dwMemoryLoad > 70 {
630                    PressureLevel::High
631                } else if mem_status.dwMemoryLoad > 50 {
632                    PressureLevel::Moderate
633                } else {
634                    PressureLevel::Normal
635                },
636                low_memory: mem_status.dwMemoryLoad > 80,
637                swapping_active: mem_status.ullTotalPageFile - mem_status.ullAvailPageFile > 0,
638                allocation_failure_rate: 0.0,
639                gc_pressure: None,
640            },
641            timestamp: Instant::now(),
642        })
643    }
644
645    #[cfg(target_os = "macos")]
646    #[allow(deprecated)] // libc::mach_host_self and mach_task_self are deprecated in favor of mach2 crate
647    fn collect_macos_stats(&self) -> Result<MemoryStats, MemoryError> {
648        use libc::{c_int, host_statistics64, mach_host_self, vm_statistics64};
649
650        // Get host port
651        let host = unsafe { mach_host_self() };
652
653        // Get VM statistics
654        let mut vm_stats: vm_statistics64 = unsafe { std::mem::zeroed() };
655        let mut count =
656            (std::mem::size_of::<vm_statistics64>() / std::mem::size_of::<c_int>()) as u32;
657
658        let result = unsafe {
659            host_statistics64(
660                host,
661                libc::HOST_VM_INFO64,
662                &mut vm_stats as *mut vm_statistics64 as *mut c_int,
663                &mut count,
664            )
665        };
666
667        // Get physical memory
668        let mut total_physical: u64 = 0;
669        unsafe {
670            let mut size = std::mem::size_of::<u64>();
671            if libc::sysctlbyname(
672                c"hw.memsize".as_ptr(),
673                &mut total_physical as *mut u64 as *mut libc::c_void,
674                &mut size,
675                std::ptr::null_mut(),
676                0,
677            ) != 0
678            {
679                // Failed to get physical memory size
680                return Err(MemoryError::SystemError(
681                    "Failed to get physical memory size via sysctl(hw.memsize)".to_string(),
682                ));
683            }
684        }
685
686        // Get page size
687        let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as u64 };
688        let page_size = if page_size == 0 { 4096 } else { page_size };
689
690        // Calculate memory values from VM statistics
691        let (physical_memory, available_physical, used_physical, cached, buffers) = if result == 0 {
692            let free = vm_stats.free_count as u64 * page_size;
693            let inactive = vm_stats.inactive_count as u64 * page_size;
694            let wired = vm_stats.wire_count as u64 * page_size;
695            let active = vm_stats.active_count as u64 * page_size;
696            let speculative = vm_stats.speculative_count as u64 * page_size;
697
698            let used = wired + active;
699            let available = free + inactive + speculative;
700            let cached_pages = inactive; // On macOS, inactive pages are similar to cache
701
702            (total_physical, available, used, cached_pages, 0)
703        } else {
704            // Fallback values if host_statistics64 fails
705            (total_physical, total_physical / 2, total_physical / 2, 0, 0)
706        };
707
708        // Get swap info - estimate from compressed memory
709        let compressed = vm_stats.compressor_page_count as u64 * page_size;
710        let swap_used_estimated = compressed; // Compressed pages often correlate with swap
711
712        // Get real swap info using sysctl
713        let (total_swap, available_swap) = unsafe {
714            let mut swap_usage: libc::xsw_usage = std::mem::zeroed();
715            let mut size = std::mem::size_of::<libc::xsw_usage>();
716            let result = libc::sysctlbyname(
717                c"vm.swapusage".as_ptr(),
718                &mut swap_usage as *mut libc::xsw_usage as *mut libc::c_void,
719                &mut size,
720                std::ptr::null_mut(),
721                0,
722            );
723
724            if result == 0 {
725                (swap_usage.xsu_total, swap_usage.xsu_avail)
726            } else {
727                // Fallback: use compressed memory as estimate
728                (compressed, 0)
729            }
730        };
731
732        // Get process memory info using task_info
733        let process_memory = unsafe {
734            let mut task_info: libc::mach_task_basic_info = std::mem::zeroed();
735            let mut count = (std::mem::size_of::<libc::mach_task_basic_info>()
736                / std::mem::size_of::<libc::natural_t>()) as u32;
737
738            let result = libc::task_info(
739                libc::mach_task_self(),
740                libc::MACH_TASK_BASIC_INFO,
741                &mut task_info as *mut libc::mach_task_basic_info as *mut libc::c_int,
742                &mut count,
743            );
744
745            if result == 0 {
746                ProcessMemoryStats {
747                    virtual_size: task_info.virtual_size,
748                    resident_size: task_info.resident_size,
749                    shared_size: 0,                        // Not directly available
750                    private_size: task_info.resident_size, // Approximation
751                    heap_size: 0,                          // Not directly available
752                    stack_size: 0,                         // Not directly available
753                    mapped_files: 0,
754                    peak_usage: task_info.resident_size_max,
755                }
756            } else {
757                // Fallback - return zero values when task_info fails
758                ProcessMemoryStats {
759                    virtual_size: 0,
760                    resident_size: 0,
761                    shared_size: 0,
762                    private_size: 0,
763                    heap_size: 0,
764                    stack_size: 0,
765                    mapped_files: 0,
766                    peak_usage: 0,
767                }
768            }
769        };
770
771        // Determine memory pressure
772        let pressure_level = if available_physical < total_physical / 10 {
773            PressureLevel::Critical
774        } else if available_physical < total_physical / 5 {
775            PressureLevel::High
776        } else if available_physical < total_physical / 3 {
777            PressureLevel::Moderate
778        } else {
779            PressureLevel::Normal
780        };
781
782        Ok(MemoryStats {
783            virtual_memory: VirtualMemoryStats {
784                // On macOS, user space virtual memory is limited by the architecture
785                // For x86_64 and ARM64, user space typically has 128TB virtual address space
786                // We use a more accurate estimate based on the process's virtual memory
787                total_virtual: process_memory.virtual_size.max(physical_memory * 2),
788                available_virtual: physical_memory,
789                used_virtual: process_memory.virtual_size,
790                reserved: process_memory.virtual_size / 4,
791                committed: process_memory.virtual_size / 4,
792            },
793            physical_memory: PhysicalMemoryStats {
794                total_physical: physical_memory,
795                available_physical,
796                used_physical,
797                cached,
798                buffers,
799                swap: SwapStats {
800                    total_swap,
801                    used_swap: swap_used_estimated,
802                    available_swap,
803                    swap_in_rate: 0.0,
804                    swap_out_rate: 0.0,
805                },
806            },
807            process_memory,
808            system_memory: SystemMemoryStats {
809                allocation_count: 0,
810                deallocation_count: 0,
811                active_allocations: 0,
812                total_allocated: 0,
813                total_deallocated: 0,
814                fragmentation_level: 0.0,
815                large_pages: LargePageStats {
816                    supported: false,
817                    total_large_pages: 0,
818                    used_large_pages: 0,
819                    page_size,
820                },
821            },
822            pressure_indicators: PressureIndicators {
823                pressure_level,
824                low_memory: pressure_level >= PressureLevel::High,
825                swapping_active: swap_used_estimated > 0,
826                allocation_failure_rate: 0.0,
827                gc_pressure: None,
828            },
829            timestamp: Instant::now(),
830        })
831    }
832
833    #[cfg(target_os = "linux")]
834    fn get_linux_system_info(&self) -> Result<SystemInfo, MemoryError> {
835        // Get OS version from /proc/sys/kernel/osrelease
836        let os_version = std::fs::read_to_string("/proc/sys/kernel/osrelease")
837            .map(|s| s.trim().to_string())
838            .unwrap_or_else(|_| "Unknown".to_string());
839
840        // Get architecture from uname
841        let architecture = unsafe {
842            let mut uname: libc::utsname = std::mem::zeroed();
843            if libc::uname(&mut uname) == 0 {
844                let machine = std::ffi::CStr::from_ptr(uname.machine.as_ptr())
845                    .to_string_lossy()
846                    .to_string();
847                machine
848            } else {
849                "unknown".to_string()
850            }
851        };
852
853        // Get CPU cores from /proc/cpuinfo
854        let cpu_cores = if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") {
855            cpuinfo
856                .lines()
857                .filter(|line| line.starts_with("processor"))
858                .count() as u32
859        } else {
860            1
861        };
862
863        // Get page size
864        let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as u64 };
865        let page_size = if page_size == 0 { 4096 } else { page_size };
866
867        // Get cache info from /proc/cpuinfo
868        let (l1_cache_size, l2_cache_size, l3_cache_size, cache_line_size) =
869            if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") {
870                let mut l1 = 0u64;
871                let mut l2 = 0u64;
872                let mut l3 = 0u64;
873                let mut line_size = 64u64;
874
875                for line in cpuinfo.lines() {
876                    if line.contains("cache size") {
877                        // Format: "cache size : 6144 KB"
878                        if let Some(kb_str) = line.split(':').nth(1) {
879                            if let Some(kb_val) = kb_str.split_whitespace().next() {
880                                if let Ok(kb) = kb_val.parse::<u64>() {
881                                    let bytes = kb * 1024;
882                                    // Heuristic: L1 < 256KB, L2 < 4MB, L3 >= 4MB
883                                    if bytes < 256 * 1024 && l1 == 0 {
884                                        l1 = bytes;
885                                    } else if bytes < 4 * 1024 * 1024 && l2 == 0 {
886                                        l2 = bytes;
887                                    } else if bytes >= 4 * 1024 * 1024 && l3 == 0 {
888                                        l3 = bytes;
889                                    }
890                                }
891                            }
892                        }
893                    }
894                    if line.contains("cache_alignment") {
895                        // Format: "cache_alignment : 64"
896                        if let Some(val_str) = line.split(':').nth(1) {
897                            if let Ok(val) = val_str.trim().parse::<u64>() {
898                                line_size = val;
899                            }
900                        }
901                    }
902                }
903
904                (l1, l2, l3, line_size)
905            } else {
906                (0, 0, 0, 64)
907            };
908
909        Ok(SystemInfo {
910            os_name: "Linux".to_string(),
911            os_version,
912            architecture,
913            cpu_cores,
914            cpu_cache: CpuCacheInfo {
915                l1_cache_size,
916                l2_cache_size,
917                l3_cache_size: if l3_cache_size > 0 {
918                    Some(l3_cache_size)
919                } else {
920                    None
921                },
922                cache_line_size,
923            },
924            page_size,
925            large_page_size: None, // Not universally supported on Linux
926            mmu_info: MmuInfo {
927                virtual_address_bits: 48,  // x86_64 typical
928                physical_address_bits: 40, // x86_64 typical
929                aslr_enabled: true,
930                nx_bit_supported: true,
931            },
932        })
933    }
934
935    #[cfg(target_os = "windows")]
936    fn get_windows_system_info(&self) -> Result<SystemInfo, MemoryError> {
937        use windows_sys::Win32::System::SystemInformation::{GetSystemInfo, SYSTEM_INFO};
938
939        let mut sys_info: SYSTEM_INFO = unsafe { std::mem::zeroed() };
940        unsafe { GetSystemInfo(&mut sys_info) };
941
942        let page_size = sys_info.dwPageSize as u64;
943        let cpu_cores = sys_info.dwNumberOfProcessors as u32;
944
945        let architecture = match unsafe { sys_info.Anonymous.Anonymous.wProcessorArchitecture } {
946            5 => "ARM",
947            6 => "ARM64",
948            9 => "x64",
949            12 => "ARM",
950            0 => "x86",
951            _ => "Unknown",
952        };
953
954        Ok(SystemInfo {
955            os_name: "Windows".to_string(),
956            os_version: std::env::var("OS").unwrap_or_else(|_| "Unknown".to_string()),
957            architecture: architecture.to_string(),
958            cpu_cores,
959            cpu_cache: CpuCacheInfo {
960                l1_cache_size: 0,
961                l2_cache_size: 0,
962                l3_cache_size: None,
963                cache_line_size: page_size,
964            },
965            page_size,
966            large_page_size: Some(sys_info.dwPageSize as u64),
967            mmu_info: MmuInfo {
968                virtual_address_bits: if unsafe {
969                    sys_info.Anonymous.Anonymous.wProcessorArchitecture
970                } == 9
971                {
972                    48
973                } else {
974                    32
975                },
976                physical_address_bits: 0,
977                aslr_enabled: true,
978                nx_bit_supported: true,
979            },
980        })
981    }
982
983    #[cfg(target_os = "macos")]
984    fn get_macos_system_info(&self) -> Result<SystemInfo, MemoryError> {
985        // Get OS version
986        let os_version = unsafe {
987            let mut size: libc::size_t = 256;
988            let mut buf = [0u8; 256];
989            if libc::sysctlbyname(
990                c"kern.osrelease".as_ptr(),
991                buf.as_mut_ptr() as *mut libc::c_void,
992                &mut size,
993                std::ptr::null_mut(),
994                0,
995            ) == 0
996                && size > 0
997            {
998                String::from_utf8_lossy(&buf[..size.min(buf.len())]).to_string()
999            } else {
1000                "Unknown".to_string()
1001            }
1002        };
1003
1004        // Get architecture
1005        let architecture = unsafe {
1006            let mut size: libc::size_t = 256;
1007            let mut buf = [0u8; 256];
1008            if libc::sysctlbyname(
1009                c"hw.machine".as_ptr(),
1010                buf.as_mut_ptr() as *mut libc::c_void,
1011                &mut size,
1012                std::ptr::null_mut(),
1013                0,
1014            ) == 0
1015                && size > 0
1016            {
1017                let arch_str = String::from_utf8_lossy(&buf[..size.min(buf.len())]).to_string();
1018                // Convert arm64, x86_64 to standard format
1019                if arch_str.contains("arm64") || arch_str.contains("arm") {
1020                    "arm64".to_string()
1021                } else {
1022                    arch_str
1023                }
1024            } else {
1025                "unknown".to_string()
1026            }
1027        };
1028
1029        // Get CPU cores
1030        let mut size = std::mem::size_of::<u32>();
1031        let mut cpu_cores: u32 = 1;
1032        unsafe {
1033            let mut mib: [libc::c_int; 2] = [libc::CTL_HW, libc::HW_NCPU];
1034            if libc::sysctl(
1035                mib.as_mut_ptr(),
1036                mib.len() as libc::c_uint,
1037                &mut cpu_cores as *mut u32 as *mut libc::c_void,
1038                &mut size,
1039                std::ptr::null_mut(),
1040                0,
1041            ) == 0
1042            {
1043                // Successfully got CPU cores
1044            }
1045        }
1046
1047        // Get page size
1048        let mut page_size: u64 = 4096;
1049        unsafe {
1050            size = std::mem::size_of::<u64>();
1051            if libc::sysctlbyname(
1052                c"hw.pagesize".as_ptr(),
1053                &mut page_size as *mut u64 as *mut libc::c_void,
1054                &mut size,
1055                std::ptr::null_mut(),
1056                0,
1057            ) != 0
1058            {
1059                page_size = 4096; // Default fallback
1060            }
1061        }
1062
1063        // Get cache line size
1064        let mut cache_line_size: u64 = 64;
1065        unsafe {
1066            size = std::mem::size_of::<u64>();
1067            if libc::sysctlbyname(
1068                c"hw.cachelinesize".as_ptr(),
1069                &mut cache_line_size as *mut u64 as *mut libc::c_void,
1070                &mut size,
1071                std::ptr::null_mut(),
1072                0,
1073            ) != 0
1074            {
1075                cache_line_size = 64; // Default fallback
1076            }
1077        }
1078
1079        // Get L1 cache size
1080        let mut l1_cache_size: u64 = 0;
1081        unsafe {
1082            size = std::mem::size_of::<u64>();
1083            if libc::sysctlbyname(
1084                c"hw.l1dcachesize".as_ptr(),
1085                &mut l1_cache_size as *mut u64 as *mut libc::c_void,
1086                &mut size,
1087                std::ptr::null_mut(),
1088                0,
1089            ) != 0
1090            {
1091                // Try alternative
1092                if libc::sysctlbyname(
1093                    c"hw.l1icachesize".as_ptr(),
1094                    &mut l1_cache_size as *mut u64 as *mut libc::c_void,
1095                    &mut size,
1096                    std::ptr::null_mut(),
1097                    0,
1098                ) != 0
1099                {
1100                    l1_cache_size = 0;
1101                }
1102            }
1103        }
1104
1105        // Get L2 cache size
1106        let mut l2_cache_size: u64 = 0;
1107        unsafe {
1108            size = std::mem::size_of::<u64>();
1109            if libc::sysctlbyname(
1110                c"hw.l2cachesize".as_ptr(),
1111                &mut l2_cache_size as *mut u64 as *mut libc::c_void,
1112                &mut size,
1113                std::ptr::null_mut(),
1114                0,
1115            ) != 0
1116            {
1117                l2_cache_size = 0;
1118            }
1119        }
1120
1121        // Get L3 cache size (may not exist on Apple Silicon)
1122        let mut l3_cache_size: u64 = 0;
1123        unsafe {
1124            size = std::mem::size_of::<u64>();
1125            if libc::sysctlbyname(
1126                c"hw.l3cachesize".as_ptr(),
1127                &mut l3_cache_size as *mut u64 as *mut libc::c_void,
1128                &mut size,
1129                std::ptr::null_mut(),
1130                0,
1131            ) != 0
1132            {
1133                l3_cache_size = 0;
1134            }
1135        }
1136
1137        Ok(SystemInfo {
1138            os_name: "macOS".to_string(),
1139            os_version,
1140            architecture,
1141            cpu_cores,
1142            cpu_cache: CpuCacheInfo {
1143                l1_cache_size,
1144                l2_cache_size,
1145                l3_cache_size: if l3_cache_size > 0 {
1146                    Some(l3_cache_size)
1147                } else {
1148                    None
1149                },
1150                cache_line_size,
1151            },
1152            page_size,
1153            large_page_size: None, // Not supported on Apple Silicon
1154            mmu_info: MmuInfo {
1155                virtual_address_bits: 48,
1156                physical_address_bits: 40,
1157                aslr_enabled: true,
1158                nx_bit_supported: true,
1159            },
1160        })
1161    }
1162}
1163
1164impl MemoryContext {
1165    fn new() -> Self {
1166        Self {
1167            initialized: false,
1168            #[cfg(target_os = "linux")]
1169            linux_context: LinuxMemoryContext {
1170                proc_meminfo_available: false,
1171                proc_status_available: false,
1172                proc_maps_available: false,
1173            },
1174            #[cfg(target_os = "windows")]
1175            windows_context: WindowsMemoryContext {
1176                global_memory_api_available: false,
1177                process_memory_api_available: false,
1178                virtual_query_available: false,
1179            },
1180            #[cfg(target_os = "macos")]
1181            macos_context: MacOSMemoryContext {
1182                vm_stat_available: false,
1183                task_info_available: false,
1184                mach_api_available: false,
1185            },
1186        }
1187    }
1188}
1189
1190/// Errors that can occur during memory information collection
1191#[derive(Debug, Clone, PartialEq)]
1192pub enum MemoryError {
1193    /// Platform not supported
1194    UnsupportedPlatform,
1195    /// Collector not initialized
1196    NotInitialized,
1197    /// Permission denied
1198    PermissionDenied,
1199    /// System API error
1200    SystemError(String),
1201    /// Parse error
1202    ParseError(String),
1203    /// I/O error
1204    IoError(String),
1205    /// Feature not implemented
1206    NotImplemented(String),
1207}
1208
1209impl Default for PlatformMemoryInfo {
1210    fn default() -> Self {
1211        Self::new()
1212    }
1213}
1214
1215impl std::fmt::Display for MemoryError {
1216    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1217        match self {
1218            MemoryError::UnsupportedPlatform => {
1219                write!(f, "Platform not supported for memory info collection")
1220            }
1221            MemoryError::NotInitialized => write!(f, "Memory info collector not initialized"),
1222            MemoryError::PermissionDenied => write!(f, "Permission denied for memory info access"),
1223            MemoryError::SystemError(msg) => write!(f, "System error: {}", msg),
1224            MemoryError::ParseError(msg) => write!(f, "Parse error: {}", msg),
1225            MemoryError::IoError(msg) => write!(f, "I/O error: {}", msg),
1226            MemoryError::NotImplemented(msg) => {
1227                write!(f, "Feature not implemented: {}", msg)
1228            }
1229        }
1230    }
1231}
1232
1233impl std::error::Error for MemoryError {}
1234
1235/// Standalone: get the CPU core of the current calling thread.
1236/// This is the shared implementation used by PlatformMemoryInfo::get_current_cpu
1237/// and by external modules that don't have a PlatformMemoryInfo instance.
1238pub fn get_current_cpu_impl() -> Option<u32> {
1239    #[cfg(any(target_os = "linux", target_os = "android"))]
1240    {
1241        let cpu = unsafe { libc::sched_getcpu() };
1242        if cpu < 0 {
1243            None
1244        } else {
1245            Some(cpu as u32)
1246        }
1247    }
1248
1249    #[cfg(target_os = "macos")]
1250    {
1251        // macOS does not expose a per-thread CPU-number API to userspace
1252        // (THREAD_IDENTIFIER_INFO returns KERN_INVALID_ARGUMENT on Apple Silicon).
1253        // THREAD_BASIC_INFO works but only gives cpu_usage %, not the core index.
1254        None
1255    }
1256
1257    #[cfg(target_os = "windows")]
1258    {
1259        extern "system" {
1260            fn GetCurrentProcessorNumber() -> u32;
1261        }
1262        unsafe { Some(GetCurrentProcessorNumber()) }
1263    }
1264
1265    #[cfg(not(any(
1266        target_os = "linux",
1267        target_os = "android",
1268        target_os = "macos",
1269        target_os = "windows"
1270    )))]
1271    {
1272        None
1273    }
1274}
1275
1276#[cfg(test)]
1277mod tests {
1278    use super::*;
1279
1280    #[test]
1281    fn test_memory_info_creation() {
1282        let info = PlatformMemoryInfo::new();
1283        assert!(!info.platform_context.initialized);
1284        assert!(info.last_stats.is_none());
1285    }
1286
1287    #[test]
1288    fn test_initialization() {
1289        let mut info = PlatformMemoryInfo::new();
1290        let result = info.initialize();
1291
1292        #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
1293        assert!(result.is_ok());
1294
1295        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
1296        assert_eq!(result, Err(MemoryError::UnsupportedPlatform));
1297    }
1298
1299    #[test]
1300    fn test_stats_collection() {
1301        let mut info = PlatformMemoryInfo::new();
1302        let _ = info.initialize();
1303
1304        let result = info.collect_stats();
1305
1306        #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
1307        {
1308            if info.platform_context.initialized {
1309                assert!(result.is_ok());
1310                let stats = result.expect("Stats should be collected");
1311                assert!(stats.physical_memory.total_physical > 0);
1312                assert!(stats.virtual_memory.total_virtual > 0);
1313            }
1314        }
1315    }
1316
1317    #[test]
1318    fn test_system_info() {
1319        let mut info = PlatformMemoryInfo::new();
1320        let _ = info.initialize();
1321
1322        let result = info.get_system_info();
1323
1324        #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
1325        {
1326            if info.platform_context.initialized {
1327                assert!(result.is_ok());
1328                let sys_info = result.expect("System info should be available");
1329                assert!(!sys_info.os_name.is_empty());
1330                assert!(sys_info.cpu_cores > 0);
1331                assert!(sys_info.page_size > 0);
1332            }
1333        }
1334    }
1335
1336    #[test]
1337    fn test_pressure_level_ordering() {
1338        assert!(PressureLevel::Critical > PressureLevel::High);
1339        assert!(PressureLevel::High > PressureLevel::Moderate);
1340        assert!(PressureLevel::Moderate > PressureLevel::Normal);
1341    }
1342
1343    #[test]
1344    fn test_collection_interval() {
1345        let mut info = PlatformMemoryInfo::new();
1346        info.set_collection_interval(Duration::from_millis(500));
1347        assert_eq!(info.collection_interval, Duration::from_millis(500));
1348    }
1349}