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 get_current_cpu(&self) -> Option<u32> {
387 get_current_cpu_impl()
388 }
389
390 pub fn get_current_cpu_standalone() -> Option<u32> {
392 get_current_cpu_impl()
393 }
394
395 pub fn set_collection_interval(&mut self, interval: Duration) {
397 self.collection_interval = interval;
398 }
399
400 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 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 self.platform_context
437 .windows_context
438 .global_memory_api_available = true; self.platform_context
440 .windows_context
441 .process_memory_api_available = true; self.platform_context
443 .windows_context
444 .virtual_query_available = true; self.platform_context.initialized = true;
447 Ok(())
448 }
449
450 #[cfg(target_os = "macos")]
451 fn initialize_macos(&mut self) -> Result<(), MemoryError> {
452 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;
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 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 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)] fn collect_macos_stats(&self) -> Result<MemoryStats, MemoryError> {
648 use libc::{c_int, host_statistics64, mach_host_self, vm_statistics64};
649
650 let host = unsafe { mach_host_self() };
652
653 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 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 return Err(MemoryError::SystemError(
681 "Failed to get physical memory size via sysctl(hw.memsize)".to_string(),
682 ));
683 }
684 }
685
686 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 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; (total_physical, available, used, cached_pages, 0)
703 } else {
704 (total_physical, total_physical / 2, total_physical / 2, 0, 0)
706 };
707
708 let compressed = vm_stats.compressor_page_count as u64 * page_size;
710 let swap_used_estimated = compressed; 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 (compressed, 0)
729 }
730 };
731
732 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, private_size: task_info.resident_size, heap_size: 0, stack_size: 0, mapped_files: 0,
754 peak_usage: task_info.resident_size_max,
755 }
756 } else {
757 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 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 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 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 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 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 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 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 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 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 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, mmu_info: MmuInfo {
927 virtual_address_bits: 48, physical_address_bits: 40, 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 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 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 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 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 }
1045 }
1046
1047 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; }
1061 }
1062
1063 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; }
1077 }
1078
1079 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 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 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 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, 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#[derive(Debug, Clone, PartialEq)]
1192pub enum MemoryError {
1193 UnsupportedPlatform,
1195 NotInitialized,
1197 PermissionDenied,
1199 SystemError(String),
1201 ParseError(String),
1203 IoError(String),
1205 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
1235pub 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 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}