diff --git a/src/common.rs b/src/common.rs index d6710be05..22ab06457 100644 --- a/src/common.rs +++ b/src/common.rs @@ -458,6 +458,9 @@ impl System { /// let s = System::new_all(); /// println!("{} bytes", s.total_memory()); /// ``` + /// + /// On Linux, if you want to see this information with the limit of your cgroup, take a look + /// at [`cgroup_limits`](System::cgroup_limits). pub fn total_memory(&self) -> u64 { self.inner.total_memory() } @@ -546,6 +549,26 @@ impl System { self.inner.used_swap() } + /// Retrieves the limits for the current cgroup (if any), otherwise it returns `None`. + /// + /// This information is computed every time the method is called. + /// + /// ⚠️ You need to have run [`refresh_memory`](System::refresh_memory) at least once before + /// calling this method. + /// + /// ⚠️ This method is only implemented for Linux. It always returns `None` for all other + /// systems. + /// + /// ```no_run + /// use sysinfo::System; + /// + /// let s = System::new_all(); + /// println!("limits: {:?}", s.cgroup_limits()); + /// ``` + pub fn cgroup_limits(&self) -> Option { + self.inner.cgroup_limits() + } + /// Returns system uptime (in seconds). /// /// ```no_run @@ -2445,6 +2468,17 @@ impl std::fmt::Display for Signal { } } +/// Contains memory limits for the current process. +#[derive(Default, Debug, Clone)] +pub struct CGroupLimits { + /// Total memory (in bytes) for the current cgroup. + pub total_memory: u64, + /// Free memory (in bytes) for the current cgroup. + pub free_memory: u64, + /// Free swap (in bytes) for the current cgroup. + pub free_swap: u64, +} + /// A struct representing system load average value. /// /// It is returned by [`System::load_average`][crate::System::load_average]. diff --git a/src/lib.rs b/src/lib.rs index 4a49be217..ea08d98b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,9 +52,9 @@ cfg_if::cfg_if! { } pub use crate::common::{ - get_current_pid, Component, Components, Cpu, CpuRefreshKind, Disk, DiskKind, DiskUsage, Disks, - Gid, Group, LoadAvg, MacAddr, NetworkData, Networks, NetworksIter, Pid, Process, - ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, System, Uid, User, Users, + get_current_pid, CGroupLimits, Component, Components, Cpu, CpuRefreshKind, Disk, DiskKind, + DiskUsage, Disks, Gid, Group, LoadAvg, MacAddr, NetworkData, Networks, NetworksIter, Pid, + Process, ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, System, Uid, User, Users, }; pub(crate) use crate::sys::{ diff --git a/src/serde.rs b/src/serde.rs index 5fb62114e..cb96580af 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -134,6 +134,22 @@ impl serde::Serialize for crate::System { } } +impl Serialize for crate::CGroupLimits { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // `3` corresponds to the number of fields. + let mut state = serializer.serialize_struct("CGroupLimits", 3)?; + + state.serialize_field("total_memory", &self.total_memory)?; + state.serialize_field("free_memory", &self.free_memory)?; + state.serialize_field("free_swap", &self.free_swap)?; + + state.end() + } +} + impl Serialize for crate::Networks { fn serialize(&self, serializer: S) -> Result where diff --git a/src/unix/apple/system.rs b/src/unix/apple/system.rs index e27628a78..c89b4c642 100644 --- a/src/unix/apple/system.rs +++ b/src/unix/apple/system.rs @@ -159,6 +159,10 @@ impl SystemInner { } } + pub(crate) fn cgroup_limits(&self) -> Option { + None + } + pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { self.cpus.refresh(refresh_kind, self.port); } diff --git a/src/unix/freebsd/system.rs b/src/unix/freebsd/system.rs index b1f2defec..1d0c25e8f 100644 --- a/src/unix/freebsd/system.rs +++ b/src/unix/freebsd/system.rs @@ -55,6 +55,10 @@ impl SystemInner { self.swap_used = swap_used; } + pub(crate) fn cgroup_limits(&self) -> Option { + None + } + pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { self.cpus.refresh(refresh_kind) } diff --git a/src/unix/linux/system.rs b/src/unix/linux/system.rs index e8a68d5e6..62168e58f 100644 --- a/src/unix/linux/system.rs +++ b/src/unix/linux/system.rs @@ -224,43 +224,10 @@ impl SystemInner { .saturating_add(self.mem_slab_reclaimable) .saturating_sub(self.mem_shmem); } - - self.refresh_cgroup(); } - fn refresh_cgroup(&mut self) { - if let (Some(mem_cur), Some(mem_max)) = ( - read_u64("/sys/fs/cgroup/memory.current"), - read_u64("/sys/fs/cgroup/memory.max"), - ) { - // cgroups v2 - self.mem_total = min(mem_max, self.mem_total); - self.mem_free = self.mem_total.saturating_sub(mem_cur); - self.mem_available = self.mem_free; - - if let Some(swap_cur) = read_u64("/sys/fs/cgroup/memory.swap.current") { - self.swap_free = self.swap_total.saturating_sub(swap_cur); - } - - read_table("/sys/fs/cgroup/memory.stat", ' ', |key, value| { - let field = match key { - "slab_reclaimable" => &mut self.mem_slab_reclaimable, - "file" => &mut self.mem_page_cache, - "shmem" => &mut self.mem_shmem, - _ => return, - }; - *field = value; - self.mem_free = self.mem_free.saturating_sub(value); - }); - } else if let (Some(mem_cur), Some(mem_max)) = ( - // cgroups v1 - read_u64("/sys/fs/cgroup/memory/memory.usage_in_bytes"), - read_u64("/sys/fs/cgroup/memory/memory.limit_in_bytes"), - ) { - self.mem_total = min(mem_max, self.mem_total); - self.mem_free = self.mem_total.saturating_sub(mem_cur); - self.mem_available = self.mem_free; - } + pub(crate) fn cgroup_limits(&self) -> Option { + crate::CGroupLimits::new(self) } pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { @@ -540,6 +507,57 @@ where } } +impl crate::CGroupLimits { + fn new(sys: &SystemInner) -> Option { + assert!( + sys.mem_total != 0, + "You need to call System::refresh_memory before trying to get cgroup limits!", + ); + if let (Some(mem_cur), Some(mem_max)) = ( + read_u64("/sys/fs/cgroup/memory.current"), + read_u64("/sys/fs/cgroup/memory.max"), + ) { + // cgroups v2 + + let mut limits = Self { + total_memory: sys.mem_total, + free_memory: sys.mem_free, + free_swap: sys.swap_free, + }; + + limits.total_memory = min(mem_max, sys.mem_total); + limits.free_memory = limits.total_memory.saturating_sub(mem_cur); + + if let Some(swap_cur) = read_u64("/sys/fs/cgroup/memory.swap.current") { + limits.free_swap = sys.swap_total.saturating_sub(swap_cur); + } + + read_table("/sys/fs/cgroup/memory.stat", ' ', |_key, value| { + limits.free_memory = limits.free_memory.saturating_sub(value); + }); + + Some(limits) + } else if let (Some(mem_cur), Some(mem_max)) = ( + // cgroups v1 + read_u64("/sys/fs/cgroup/memory/memory.usage_in_bytes"), + read_u64("/sys/fs/cgroup/memory/memory.limit_in_bytes"), + ) { + let mut limits = Self { + total_memory: sys.mem_total, + free_memory: sys.mem_free, + free_swap: sys.swap_free, + }; + + limits.total_memory = min(mem_max, sys.mem_total); + limits.free_memory = limits.total_memory.saturating_sub(mem_cur); + + Some(limits) + } else { + None + } + } +} + #[derive(PartialEq, Eq)] enum InfoType { /// The end-user friendly name of: diff --git a/src/unknown/system.rs b/src/unknown/system.rs index c16a332f3..1201aa4b6 100644 --- a/src/unknown/system.rs +++ b/src/unknown/system.rs @@ -21,6 +21,10 @@ impl SystemInner { pub(crate) fn refresh_memory(&mut self) {} + pub(crate) fn cgroup_limits(&self) -> Option { + None + } + pub(crate) fn refresh_cpu_specifics(&mut self, _refresh_kind: CpuRefreshKind) {} pub(crate) fn refresh_processes_specifics(&mut self, _refresh_kind: ProcessRefreshKind) {} diff --git a/src/windows/system.rs b/src/windows/system.rs index 9bbde2d42..26388dd70 100644 --- a/src/windows/system.rs +++ b/src/windows/system.rs @@ -165,6 +165,10 @@ impl SystemInner { } } + pub(crate) fn cgroup_limits(&self) -> Option { + None + } + #[allow(clippy::map_entry)] pub(crate) fn refresh_process_specifics( &mut self,