]> git.lizzy.rs Git - rust.git/blob - crates/profile/src/memory_usage.rs
Add support for mallinfo2 on glibc Linux
[rust.git] / crates / profile / src / memory_usage.rs
1 //! Like [`std::time::Instant`], but for memory.
2 //!
3 //! Measures the total size of all currently allocated objects.
4 use std::fmt;
5
6 use cfg_if::cfg_if;
7
8 #[derive(Copy, Clone)]
9 pub struct MemoryUsage {
10     pub allocated: Bytes,
11 }
12
13 impl fmt::Display for MemoryUsage {
14     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
15         write!(fmt, "{}", self.allocated)
16     }
17 }
18
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 }
23     }
24 }
25
26 impl MemoryUsage {
27     pub fn now() -> MemoryUsage {
28         cfg_if! {
29             if #[cfg(all(feature = "jemalloc", not(target_env = "msvc")))] {
30                 jemalloc_ctl::epoch::advance().unwrap();
31                 MemoryUsage {
32                     allocated: Bytes(jemalloc_ctl::stats::allocated::read().unwrap() as isize),
33                 }
34             } else if #[cfg(all(target_os = "linux", target_env = "gnu"))] {
35                 memusage_linux()
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.
39
40                 use winapi::um::processthreadsapi::*;
41                 use winapi::um::psapi::*;
42                 use std::mem::{MaybeUninit, size_of};
43
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) };
48                 assert!(ret != 0);
49
50                 let usage = unsafe { mem_counters.assume_init().PagefileUsage };
51                 MemoryUsage { allocated: Bytes(usage as isize) }
52             } else {
53                 MemoryUsage { allocated: Bytes(0) }
54             }
55         }
56     }
57 }
58
59 #[cfg(all(target_os = "linux", target_env = "gnu"))]
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.
65
66     use std::ffi::CStr;
67     use std::sync::atomic::{AtomicUsize, Ordering};
68
69     static MALLINFO2: AtomicUsize = AtomicUsize::new(1);
70
71     let mut mallinfo2 = MALLINFO2.load(Ordering::Relaxed);
72     if mallinfo2 == 1 {
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);
77     }
78
79     if mallinfo2 == 0 {
80         // mallinfo2 does not exist, use mallinfo.
81         let alloc = unsafe { libc::mallinfo() }.uordblks as isize;
82         MemoryUsage { allocated: Bytes(alloc) }
83     } else {
84         let mallinfo2: fn() -> libc::mallinfo2 = unsafe { std::mem::transmute(mallinfo2) };
85         let alloc = mallinfo2().uordblks as isize;
86         MemoryUsage { allocated: Bytes(alloc) }
87     }
88 }
89
90 #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
91 pub struct Bytes(isize);
92
93 impl Bytes {
94     pub fn megabytes(self) -> isize {
95         self.0 / 1024 / 1024
96     }
97 }
98
99 impl fmt::Display for Bytes {
100     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101         let bytes = self.0;
102         let mut value = bytes;
103         let mut suffix = "b";
104         if value.abs() > 4096 {
105             value /= 1024;
106             suffix = "kb";
107             if value.abs() > 4096 {
108                 value /= 1024;
109                 suffix = "mb";
110             }
111         }
112         f.pad(&format!("{}{}", value, suffix))
113     }
114 }
115
116 impl std::ops::AddAssign<usize> for Bytes {
117     fn add_assign(&mut self, x: usize) {
118         self.0 += x as isize;
119     }
120 }
121
122 impl std::ops::Sub for Bytes {
123     type Output = Bytes;
124     fn sub(self, rhs: Bytes) -> Bytes {
125         Bytes(self.0 - rhs.0)
126     }
127 }