1 //! Like [`std::time::Instant`], but for memory.
3 //! Measures the total size of all currently allocated objects.
9 pub struct MemoryUsage {
13 impl fmt::Display for MemoryUsage {
14 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 impl std::ops::Sub for MemoryUsage {
20 type Output = MemoryUsage;
21 fn sub(self, rhs: MemoryUsage) -> MemoryUsage {
22 MemoryUsage { allocated: self.allocated - rhs.allocated }
27 pub fn now() -> MemoryUsage {
29 if #[cfg(all(feature = "jemalloc", not(target_env = "msvc")))] {
30 jemalloc_ctl::epoch::advance().unwrap();
32 allocated: Bytes(jemalloc_ctl::stats::allocated::read().unwrap() as isize),
34 } else if #[cfg(all(target_os = "linux", target_env = "gnu"))] {
36 } else if #[cfg(windows)] {
37 // There doesn't seem to be an API for determining heap usage, so we try to
38 // approximate that by using the Commit Charge value.
40 use winapi::um::processthreadsapi::*;
41 use winapi::um::psapi::*;
42 use std::mem::{MaybeUninit, size_of};
44 let proc = unsafe { GetCurrentProcess() };
45 let mut mem_counters = MaybeUninit::uninit();
46 let cb = size_of::<PROCESS_MEMORY_COUNTERS>();
47 let ret = unsafe { GetProcessMemoryInfo(proc, mem_counters.as_mut_ptr(), cb as u32) };
50 let usage = unsafe { mem_counters.assume_init().PagefileUsage };
51 MemoryUsage { allocated: Bytes(usage as isize) }
53 MemoryUsage { allocated: Bytes(0) }
59 #[cfg(all(target_os = "linux", target_env = "gnu", not(feature = "jemalloc")))]
60 fn memusage_linux() -> MemoryUsage {
61 // Linux/glibc has 2 APIs for allocator introspection that we can use: mallinfo and mallinfo2.
62 // mallinfo uses `int` fields and cannot handle memory usage exceeding 2 GB.
63 // mallinfo2 is very recent, so its presence needs to be detected at runtime.
64 // Both are abysmally slow.
67 use std::sync::atomic::{AtomicUsize, Ordering};
69 static MALLINFO2: AtomicUsize = AtomicUsize::new(1);
71 let mut mallinfo2 = MALLINFO2.load(Ordering::Relaxed);
73 let cstr = CStr::from_bytes_with_nul(b"mallinfo2\0").unwrap();
74 mallinfo2 = unsafe { libc::dlsym(libc::RTLD_DEFAULT, cstr.as_ptr()) } as usize;
75 // NB: races don't matter here, since they'll always store the same value
76 MALLINFO2.store(mallinfo2, Ordering::Relaxed);
80 // mallinfo2 does not exist, use mallinfo.
81 let alloc = unsafe { libc::mallinfo() }.uordblks as isize;
82 MemoryUsage { allocated: Bytes(alloc) }
84 let mallinfo2: fn() -> libc::mallinfo2 = unsafe { std::mem::transmute(mallinfo2) };
85 let alloc = mallinfo2().uordblks as isize;
86 MemoryUsage { allocated: Bytes(alloc) }
90 #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
91 pub struct Bytes(isize);
94 pub fn megabytes(self) -> isize {
99 impl fmt::Display for Bytes {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 let mut value = bytes;
103 let mut suffix = "b";
104 if value.abs() > 4096 {
107 if value.abs() > 4096 {
112 f.pad(&format!("{}{}", value, suffix))
116 impl std::ops::AddAssign<usize> for Bytes {
117 fn add_assign(&mut self, x: usize) {
118 self.0 += x as isize;
122 impl std::ops::Sub for Bytes {
124 fn sub(self, rhs: Bytes) -> Bytes {
125 Bytes(self.0 - rhs.0)