1use std::time::{Duration, Instant};
2
3pub struct PlatformMemoryInfo {
5 last_stats: Option<MemoryStats>,
7 collection_interval: Duration,
9 last_collection: Option<Instant>,
11 platform_context: MemoryContext,
13}
14
15#[derive(Debug, Clone)]
17pub struct MemoryStats {
18 pub virtual_memory: VirtualMemoryStats,
20 pub physical_memory: PhysicalMemoryStats,
22 pub process_memory: ProcessMemoryStats,
24 pub system_memory: SystemMemoryStats,
26 pub pressure_indicators: PressureIndicators,
28 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#[derive(Debug, Clone, Default)]
47pub struct VirtualMemoryStats {
48 pub total_virtual: u64,
50 pub available_virtual: u64,
52 pub used_virtual: u64,
54 pub reserved: u64,
56 pub committed: u64,
58}
59
60#[derive(Debug, Clone, Default)]
62pub struct PhysicalMemoryStats {
63 pub total_physical: u64,
65 pub available_physical: u64,
67 pub used_physical: u64,
69 pub cached: u64,
71 pub buffers: u64,
73 pub swap: SwapStats,
75}
76
77#[derive(Debug, Clone)]
79pub struct SwapStats {
80 pub total_swap: u64,
82 pub used_swap: u64,
84 pub available_swap: u64,
86 pub swap_in_rate: f64,
88 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#[derive(Debug, Clone, Default)]
106pub struct ProcessMemoryStats {
107 pub virtual_size: u64,
109 pub resident_size: u64,
111 pub shared_size: u64,
113 pub private_size: u64,
115 pub heap_size: u64,
117 pub stack_size: u64,
119 pub mapped_files: u64,
121 pub peak_usage: u64,
123}
124
125#[derive(Debug, Clone)]
127pub struct SystemMemoryStats {
128 pub allocation_count: u64,
130 pub deallocation_count: u64,
132 pub active_allocations: u64,
134 pub total_allocated: u64,
136 pub total_deallocated: u64,
138 pub fragmentation_level: f64,
140 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#[derive(Debug, Clone, Default)]
160pub struct LargePageStats {
161 pub supported: bool,
163 pub total_large_pages: u64,
165 pub used_large_pages: u64,
167 pub page_size: u64,
169}
170
171#[derive(Debug, Clone)]
173pub struct PressureIndicators {
174 pub pressure_level: PressureLevel,
176 pub low_memory: bool,
178 pub swapping_active: bool,
180 pub allocation_failure_rate: f64,
182 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
200pub enum PressureLevel {
201 #[default]
203 Normal,
204 Moderate,
206 High,
208 Critical,
210}
211
212#[derive(Debug, Clone)]
214pub struct SystemInfo {
215 pub os_name: String,
217 pub os_version: String,
219 pub architecture: String,
221 pub cpu_cores: u32,
223 pub cpu_cache: CpuCacheInfo,
225 pub page_size: u64,
227 pub large_page_size: Option<u64>,
229 pub mmu_info: MmuInfo,
231}
232
233#[derive(Debug, Clone)]
235pub struct CpuCacheInfo {
236 pub l1_cache_size: u64,
238 pub l2_cache_size: u64,
240 pub l3_cache_size: Option<u64>,
242 pub cache_line_size: u64,
244}
245
246#[derive(Debug, Clone)]
248pub struct MmuInfo {
249 pub virtual_address_bits: u32,
251 pub physical_address_bits: u32,
253 pub aslr_enabled: bool,
255 pub nx_bit_supported: bool,
257}
258
259#[derive(Debug)]
261struct MemoryContext {
262 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 proc_meminfo_available: bool,
280 proc_status_available: bool,
282 proc_maps_available: bool,
284}
285
286#[cfg(target_os = "windows")]
287#[derive(Debug)]
288struct WindowsMemoryContext {
289 global_memory_api_available: bool,
291 process_memory_api_available: bool,
293 virtual_query_available: bool,
295}
296
297#[cfg(target_os = "macos")]
298#[derive(Debug)]
299struct MacOSMemoryContext {
300 vm_stat_available: bool,
302 task_info_available: bool,
304 mach_api_available: bool,
306}
307
308impl PlatformMemoryInfo {
309 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 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 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 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 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 pub fn set_collection_interval(&mut self, interval: Duration) {
387 self.collection_interval = interval;
388 }
389
390 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 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 self.platform_context
427 .windows_context
428 .global_memory_api_available = true; self.platform_context
430 .windows_context
431 .process_memory_api_available = true; self.platform_context
433 .windows_context
434 .virtual_query_available = true; self.platform_context.initialized = true;
437 Ok(())
438 }
439
440 #[cfg(target_os = "macos")]
441 fn initialize_macos(&mut self) -> Result<(), MemoryError> {
442 self.platform_context.macos_context.vm_stat_available = true; self.platform_context.macos_context.task_info_available = true; self.platform_context.macos_context.mach_api_available = true; 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 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 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)] fn collect_macos_stats(&self) -> Result<MemoryStats, MemoryError> {
638 use libc::{c_int, host_statistics64, mach_host_self, vm_statistics64};
639
640 let host = unsafe { mach_host_self() };
642
643 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 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 return Err(MemoryError::SystemError(
671 "Failed to get physical memory size via sysctl(hw.memsize)".to_string(),
672 ));
673 }
674 }
675
676 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 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; (total_physical, available, used, cached_pages, 0)
693 } else {
694 (total_physical, total_physical / 2, total_physical / 2, 0, 0)
696 };
697
698 let compressed = vm_stats.compressor_page_count as u64 * page_size;
700 let swap_used_estimated = compressed; 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 (compressed, 0)
719 }
720 };
721
722 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, private_size: task_info.resident_size, heap_size: 0, stack_size: 0, mapped_files: 0,
744 peak_usage: task_info.resident_size_max,
745 }
746 } else {
747 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 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 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 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 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 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 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 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 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 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 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, mmu_info: MmuInfo {
917 virtual_address_bits: 48, physical_address_bits: 40, 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 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 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 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 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 }
1035 }
1036
1037 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; }
1051 }
1052
1053 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; }
1067 }
1068
1069 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 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 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 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, 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#[derive(Debug, Clone, PartialEq)]
1182pub enum MemoryError {
1183 UnsupportedPlatform,
1185 NotInitialized,
1187 PermissionDenied,
1189 SystemError(String),
1191 ParseError(String),
1193 IoError(String),
1195 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}