re_memory/
memory_limit.rs

1use saturating_cast::SaturatingCast as _;
2
3/// Represents a limit in how much RAM to use for the entire process.
4///
5/// Different systems can chose to heed the memory limit in different ways,
6/// e.g. by dropping old data when it is exceeded.
7///
8/// It is recommended that they log using [`re_log::info_once`] when they
9/// drop data because a memory limit is reached.
10#[derive(Copy, Clone, Debug, PartialEq, Eq)]
11pub struct MemoryLimit {
12    /// Limit in bytes.
13    ///
14    /// This is primarily compared to what is reported by [`crate::AccountingAllocator`] ('counted').
15    /// We limit based on this instead of `resident` (RSS) because `counted` is what we have immediate
16    /// control over, while RSS depends on what our allocator (MiMalloc) decides to do.
17    pub max_bytes: Option<i64>,
18}
19
20impl MemoryLimit {
21    /// No limit.
22    pub const UNLIMITED: Self = Self { max_bytes: None };
23
24    /// Set the limit to some number of bytes.
25    pub fn from_bytes(max_bytes: u64) -> Self {
26        Self {
27            max_bytes: Some(max_bytes.saturating_cast()),
28        }
29    }
30
31    /// Set the limit to some fraction (0-1) of the total available RAM.
32    pub fn from_fraction_of_total(fraction: f32) -> Self {
33        let total_memory = crate::total_ram_in_bytes();
34        if let Some(total_memory) = total_memory {
35            let max_bytes = (fraction as f64 * total_memory as f64).round();
36
37            re_log::debug!(
38                "Setting memory limit to {}, which is {}% of total available memory ({}).",
39                re_format::format_bytes(max_bytes),
40                100.0 * fraction,
41                re_format::format_bytes(total_memory as _),
42            );
43
44            Self {
45                max_bytes: Some(max_bytes as _),
46            }
47        } else {
48            re_log::info!("Couldn't determine total available memory. Setting no memory limit.");
49            Self { max_bytes: None }
50        }
51    }
52
53    /// The limit can either be absolute (e.g. "16GB") or relative (e.g. "50%").
54    pub fn parse(limit: &str) -> Result<Self, String> {
55        if let Some(percentage) = limit.strip_suffix('%') {
56            let percentage = percentage
57                .parse::<f32>()
58                .map_err(|_err| format!("expected e.g. '50%', got {limit:?}"))?;
59            let fraction = percentage / 100.0;
60            Ok(Self::from_fraction_of_total(fraction))
61        } else {
62            re_format::parse_bytes(limit)
63                .map(|max_bytes| Self {
64                    max_bytes: Some(max_bytes),
65                })
66                .ok_or_else(|| format!("expected e.g. '16GB', got {limit:?}"))
67        }
68    }
69
70    #[inline]
71    pub fn is_limited(&self) -> bool {
72        self.max_bytes.is_some()
73    }
74
75    #[inline]
76    pub fn is_unlimited(&self) -> bool {
77        self.max_bytes.is_none()
78    }
79
80    /// Returns how large fraction of memory we should free to go down to the exact limit.
81    pub fn is_exceeded_by(&self, mem_use: &crate::MemoryUse) -> Option<f32> {
82        let max_bytes = self.max_bytes?;
83
84        if let Some(counted_use) = mem_use.counted {
85            if max_bytes < counted_use {
86                return Some((counted_use - max_bytes) as f32 / counted_use as f32);
87            }
88        } else if let Some(resident_use) = mem_use.resident {
89            re_log::warn_once!(
90                "Using resident memory use (RSS) for memory limiting, because a memory tracker was not available."
91            );
92            if max_bytes < resident_use {
93                return Some((resident_use - max_bytes) as f32 / resident_use as f32);
94            }
95        }
96
97        None
98    }
99}
100
101impl std::str::FromStr for MemoryLimit {
102    type Err = String;
103
104    fn from_str(s: &str) -> Result<Self, Self::Err> {
105        Self::parse(s)
106    }
107}