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// Only this probe uses the cgroup helpers, so the module lives here rather
19// than at the crate root. The file carries more surface than the probe
20// needs, hence the dead-code allowance.
21#[cfg(target_os = "linux")]
22#[path = "cgroup.rs"]
23#[allow(dead_code)]
24mod cgroup;
25
26/// Returns the physical memory available to this process in bytes: the
27/// host's RAM, clamped by the cgroup memory limit when one is set. Both
28/// cgroup v1 and v2 are honored, resolved through `/proc/self/mountinfo`
29/// and `/proc/self/cgroup` rather than an assumed mount path, so containers
30/// (including host-namespace cgroup mounts) do not derive budgets from host
31/// RAM. `None` if detection fails.
32///
33/// Deliberately distinct from any *announced* memory limit: on nodes whose
34/// disk is provisioned as swap, the announced limit includes swap so the
35/// memory limiter can bound total heap. Budgets that bound *resident* bytes
36/// must instead derive from memory that can be resident, which is what this
37/// reports.
38pub fn physical_memory_bytes() -> Option<usize> {
39    let host = host_memory_bytes()?;
40    match cgroup_memory_max() {
41        Some(limit) if limit < host => Some(limit),
42        _ => Some(host),
43    }
44}
45
46#[cfg(target_os = "linux")]
47fn host_memory_bytes() -> Option<usize> {
48    let meminfo = std::fs::read_to_string("/proc/meminfo").ok()?;
49    let line = meminfo.lines().find(|l| l.starts_with("MemTotal:"))?;
50    let kib: usize = line.split_whitespace().nth(1)?.parse().ok()?;
51    Some(kib * 1024)
52}
53
54#[cfg(target_os = "macos")]
55fn host_memory_bytes() -> Option<usize> {
56    let mut size: u64 = 0;
57    let mut len = std::mem::size_of::<u64>();
58    // SAFETY: `sysctlbyname` reads into an out-buffer of the size we report;
59    // `hw.memsize` is a `u64` and `len` matches.
60    let ret = unsafe {
61        libc::sysctlbyname(
62            c"hw.memsize".as_ptr(),
63            std::ptr::from_mut(&mut size).cast::<libc::c_void>(),
64            &mut len,
65            std::ptr::null_mut(),
66            0,
67        )
68    };
69    if ret == 0 {
70        usize::try_from(size).ok()
71    } else {
72        None
73    }
74}
75
76#[cfg(not(any(target_os = "linux", target_os = "macos")))]
77fn host_memory_bytes() -> Option<usize> {
78    None
79}
80
81/// The RAM limit of the cgroup governing this process, if any.
82#[cfg(target_os = "linux")]
83fn cgroup_memory_max() -> Option<usize> {
84    cgroup::detect_memory_limit()?.max
85}
86
87#[cfg(not(target_os = "linux"))]
88fn cgroup_memory_max() -> Option<usize> {
89    None
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[mz_ore::test]
97    fn detects_some_memory() {
98        let bytes = physical_memory_bytes().expect("detection works on test platforms");
99        // Sanity: more than 64 MiB, less than 1 PiB.
100        assert!(bytes > 64 << 20);
101        assert!(bytes < 1 << 50);
102    }
103}