Skip to main content

mz_ore/
memory.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License in the LICENSE file at the
6// root of this repository, or online at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Physical memory introspection.
17
18/// Returns the physical memory available to this process in bytes: the
19/// host's RAM, clamped by the cgroup memory limit when one is set. Both
20/// cgroup v1 and v2 are honored, resolved through `/proc/self/mountinfo`
21/// and `/proc/self/cgroup` rather than an assumed mount path, so containers
22/// (including host-namespace cgroup mounts) do not derive budgets from host
23/// RAM. `None` if detection fails.
24///
25/// Deliberately distinct from any *announced* memory limit: on nodes whose
26/// disk is provisioned as swap, the announced limit includes swap so the
27/// memory limiter can bound total heap. Budgets that bound *resident* bytes
28/// must instead derive from memory that can be resident, which is what this
29/// reports.
30pub fn physical_memory_bytes() -> Option<usize> {
31    let host = host_memory_bytes()?;
32    match cgroup_memory_max() {
33        Some(limit) if limit < host => Some(limit),
34        _ => Some(host),
35    }
36}
37
38#[cfg(target_os = "linux")]
39fn host_memory_bytes() -> Option<usize> {
40    let meminfo = std::fs::read_to_string("/proc/meminfo").ok()?;
41    let line = meminfo.lines().find(|l| l.starts_with("MemTotal:"))?;
42    let kib: usize = line.split_whitespace().nth(1)?.parse().ok()?;
43    Some(kib * 1024)
44}
45
46#[cfg(target_os = "macos")]
47fn host_memory_bytes() -> Option<usize> {
48    let mut size: u64 = 0;
49    let mut len = std::mem::size_of::<u64>();
50    // SAFETY: `sysctlbyname` reads into an out-buffer of the size we report;
51    // `hw.memsize` is a `u64` and `len` matches.
52    let ret = unsafe {
53        libc::sysctlbyname(
54            c"hw.memsize".as_ptr(),
55            std::ptr::from_mut(&mut size).cast::<libc::c_void>(),
56            &mut len,
57            std::ptr::null_mut(),
58            0,
59        )
60    };
61    if ret == 0 {
62        usize::try_from(size).ok()
63    } else {
64        None
65    }
66}
67
68#[cfg(not(any(target_os = "linux", target_os = "macos")))]
69fn host_memory_bytes() -> Option<usize> {
70    None
71}
72
73/// The RAM limit of the cgroup governing this process, if any.
74#[cfg(target_os = "linux")]
75fn cgroup_memory_max() -> Option<usize> {
76    crate::cgroup::detect_memory_limit()?.max
77}
78
79#[cfg(not(target_os = "linux"))]
80fn cgroup_memory_max() -> Option<usize> {
81    None
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[mz_ore::test]
89    fn detects_some_memory() {
90        let bytes = physical_memory_bytes().expect("detection works on test platforms");
91        // Sanity: more than 64 MiB, less than 1 PiB.
92        assert!(bytes > 64 << 20);
93        assert!(bytes < 1 << 50);
94    }
95}