Repository: GuillaumeGomez/sysinfo
Pull request #1172 (2023-12-19 17:35:37)

Create new MemoryRefreshKind type and new `System::refresh_memory_specifics` method
Fixes https://github.com/GuillaumeGomez/sysinfo/issues/1167.


Patch:
diff --git a/src/common.rs b/src/common.rs
index 9a0a555..353e192 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -105,8 +105,8 @@ impl System {
     /// );
     /// ```
     pub fn refresh_specifics(&mut self, refreshes: RefreshKind) {
-        if refreshes.memory() {
-            self.refresh_memory();
+        if let Some(kind) = refreshes.memory() {
+            self.refresh_memory_specifics(kind);
         }
         if let Some(kind) = refreshes.cpu() {
             self.refresh_cpu_specifics(kind);
@@ -135,6 +135,10 @@ impl System {
 
     /// Refreshes RAM and SWAP usage.
     ///
+    /// It is the same as calling `system.refresh_memory_specifics(MemoryRefreshKind::everything())`.
+    ///
+    /// If you don't want to refresh both, take a look at [`System::refresh_memory_specifics`].
+    ///
     /// ```no_run
     /// use sysinfo::System;
     ///
@@ -142,7 +146,19 @@ impl System {
     /// s.refresh_memory();
     /// ```
     pub fn refresh_memory(&mut self) {
-        self.inner.refresh_memory()
+        self.refresh_memory_specifics(MemoryRefreshKind::everything())
+    }
+
+    /// Refreshes system memory specific information.
+    ///
+    /// ```no_run
+    /// use sysinfo::{MemoryRefreshKind, System};
+    ///
+    /// let mut s = System::new();
+    /// s.refresh_memory_specifics(MemoryRefreshKind::new().with_ram());
+    /// ```
+    pub fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
+        self.inner.refresh_memory_specifics(refresh_kind)
     }
 
     /// Refreshes CPUs usage.
@@ -1731,6 +1747,7 @@ impl CpuRefreshKind {
     /// let r = CpuRefreshKind::new();
     ///
     /// assert_eq!(r.frequency(), false);
+    /// assert_eq!(r.cpu_usage(), false);
     /// ```
     pub fn new() -> Self {
         Self::default()
@@ -1744,6 +1761,7 @@ impl CpuRefreshKind {
     /// let r = CpuRefreshKind::everything();
     ///
     /// assert_eq!(r.frequency(), true);
+    /// assert_eq!(r.cpu_usage(), true);
     /// ```
     pub fn everything() -> Self {
         Self {
@@ -1756,6 +1774,65 @@ impl CpuRefreshKind {
     impl_get_set!(CpuRefreshKind, frequency, with_frequency, without_frequency);
 }
 
+/// Used to determine which memory you want to refresh specifically.
+///
+/// ⚠️ Just like all other refresh types, ruling out a refresh doesn't assure you that
+/// the information won't be retrieved if the information is accessible without needing
+/// extra computation.
+///
+/// ```
+/// use sysinfo::{MemoryRefreshKind, System};
+///
+/// let mut system = System::new();
+///
+/// // We don't want to update all memories information.
+/// system.refresh_memory_specifics(MemoryRefreshKind::new().with_ram());
+///
+/// println!("total RAM: {}", system.total_memory());
+/// println!("free RAM:  {}", system.free_memory());
+/// ```
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub struct MemoryRefreshKind {
+    ram: bool,
+    swap: bool,
+}
+
+impl MemoryRefreshKind {
+    /// Creates a new `MemoryRefreshKind` with every refresh set to `false`.
+    ///
+    /// ```
+    /// use sysinfo::MemoryRefreshKind;
+    ///
+    /// let r = MemoryRefreshKind::new();
+    ///
+    /// assert_eq!(r.ram(), false);
+    /// assert_eq!(r.swap(), false);
+    /// ```
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Creates a new `MemoryRefreshKind` with every refresh set to `true`.
+    ///
+    /// ```
+    /// use sysinfo::MemoryRefreshKind;
+    ///
+    /// let r = MemoryRefreshKind::everything();
+    ///
+    /// assert_eq!(r.ram(), true);
+    /// assert_eq!(r.swap(), true);
+    /// ```
+    pub fn everything() -> Self {
+        Self {
+            ram: true,
+            swap: true,
+        }
+    }
+
+    impl_get_set!(MemoryRefreshKind, ram, with_ram, without_ram);
+    impl_get_set!(MemoryRefreshKind, swap, with_swap, without_swap);
+}
+
 /// Used to determine what you want to refresh specifically on the [`System`][crate::System] type.
 ///
 /// ⚠️ Just like all other refresh types, ruling out a refresh doesn't assure you that
@@ -1776,7 +1853,7 @@ impl CpuRefreshKind {
 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
 pub struct RefreshKind {
     processes: Option<ProcessRefreshKind>,
-    memory: bool,
+    memory: Option<MemoryRefreshKind>,
     cpu: Option<CpuRefreshKind>,
 }
 
@@ -1789,7 +1866,7 @@ impl RefreshKind {
     /// let r = RefreshKind::new();
     ///
     /// assert_eq!(r.processes().is_some(), false);
-    /// assert_eq!(r.memory(), false);
+    /// assert_eq!(r.memory().is_some(), false);
     /// assert_eq!(r.cpu().is_some(), false);
     /// ```
     pub fn new() -> Self {
@@ -1804,13 +1881,13 @@ impl RefreshKind {
     /// let r = RefreshKind::everything();
     ///
     /// assert_eq!(r.processes().is_some(), true);
-    /// assert_eq!(r.memory(), true);
+    /// assert_eq!(r.memory().is_some(), true);
     /// assert_eq!(r.cpu().is_some(), true);
     /// ```
     pub fn everything() -> Self {
         Self {
             processes: Some(ProcessRefreshKind::everything()),
-            memory: true,
+            memory: Some(MemoryRefreshKind::everything()),
             cpu: Some(CpuRefreshKind::everything()),
         }
     }
@@ -1822,7 +1899,13 @@ impl RefreshKind {
         without_processes,
         ProcessRefreshKind
     );
-    impl_get_set!(RefreshKind, memory, with_memory, without_memory);
+    impl_get_set!(
+        RefreshKind,
+        memory,
+        with_memory,
+        without_memory,
+        MemoryRefreshKind
+    );
     impl_get_set!(RefreshKind, cpu, with_cpu, without_cpu, CpuRefreshKind);
 }
 
@@ -3692,64 +3775,3 @@ impl Cpu {
         self.inner.frequency()
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::{MacAddr, ProcessStatus};
-
-    // This test only exists to ensure that the `Display` and `Debug` traits are implemented on the
-    // `ProcessStatus` enum on all targets.
-    #[test]
-    fn check_display_impl_process_status() {
-        println!("{} {:?}", ProcessStatus::Parked, ProcessStatus::Idle);
-    }
-
-    // Ensure that the `Display` and `Debug` traits are implemented on the `MacAddr` struct
-    #[test]
-    fn check_display_impl_mac_address() {
-        println!(
-            "{} {:?}",
-            MacAddr([0x1, 0x2, 0x3, 0x4, 0x5, 0x6]),
-            MacAddr([0xa, 0xb, 0xc, 0xd, 0xe, 0xf])
-        );
-    }
-
-    #[test]
-    fn check_mac_address_is_unspecified_true() {
-        assert!(MacAddr::UNSPECIFIED.is_unspecified());
-        assert!(MacAddr([0; 6]).is_unspecified());
-    }
-
-    #[test]
-    fn check_mac_address_is_unspecified_false() {
-        assert!(!MacAddr([1, 2, 3, 4, 5, 6]).is_unspecified());
-    }
-
-    // This test exists to ensure that the `TryFrom<usize>` and `FromStr` traits are implemented
-    // on `Uid`, `Gid` and `Pid`.
-    #[allow(clippy::unnecessary_fallible_conversions)]
-    #[test]
-    fn check_uid_gid_from_impls() {
-        use std::convert::TryFrom;
-        use std::str::FromStr;
-
-        #[cfg(not(windows))]
-        {
-            assert!(crate::Uid::try_from(0usize).is_ok());
-            assert!(crate::Uid::from_str("0").is_ok());
-        }
-        #[cfg(windows)]
-        {
-            assert!(crate::Uid::from_str("S-1-5-18").is_ok()); // SECURITY_LOCAL_SYSTEM_RID
-            assert!(crate::Uid::from_str("0").is_err());
-        }
-
-        assert!(crate::Gid::try_from(0usize).is_ok());
-        assert!(crate::Gid::from_str("0").is_ok());
-
-        assert!(crate::Pid::try_from(0usize).is_ok());
-        // If it doesn't panic, it's fine.
-        let _ = crate::Pid::from(0);
-        assert!(crate::Pid::from_str("0").is_ok());
-    }
-}
diff --git a/src/lib.rs b/src/lib.rs
index faf1cc0..8b3a3dc 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -52,8 +52,9 @@ cfg_if::cfg_if! {
 
 pub use crate::common::{
     get_current_pid, CGroupLimits, Component, Components, Cpu, CpuRefreshKind, Disk, DiskKind,
-    DiskUsage, Disks, Gid, Group, LoadAvg, MacAddr, NetworkData, Networks, Pid, Process,
-    ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, System, Uid, UpdateKind, User, Users,
+    DiskUsage, Disks, Gid, Group, LoadAvg, MacAddr, MemoryRefreshKind, NetworkData, Networks, Pid,
+    Process, ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, System, Uid, UpdateKind, User,
+    Users,
 };
 
 pub(crate) use crate::sys::{
@@ -71,7 +72,6 @@ mod common;
 mod debug;
 #[cfg(feature = "serde")]
 mod serde;
-mod system;
 mod utils;
 
 /// This function is only used on Linux targets, on the other platforms it does nothing and returns
@@ -513,4 +513,60 @@ mod test {
     fn check_cpu_arch() {
         assert_eq!(System::cpu_arch().is_some(), IS_SUPPORTED);
     }
+
+    // This test only exists to ensure that the `Display` and `Debug` traits are implemented on the
+    // `ProcessStatus` enum on all targets.
+    #[test]
+    fn check_display_impl_process_status() {
+        println!("{} {:?}", ProcessStatus::Parked, ProcessStatus::Idle);
+    }
+
+    // Ensure that the `Display` and `Debug` traits are implemented on the `MacAddr` struct
+    #[test]
+    fn check_display_impl_mac_address() {
+        println!(
+            "{} {:?}",
+            MacAddr([0x1, 0x2, 0x3, 0x4, 0x5, 0x6]),
+            MacAddr([0xa, 0xb, 0xc, 0xd, 0xe, 0xf])
+        );
+    }
+
+    #[test]
+    fn check_mac_address_is_unspecified_true() {
+        assert!(MacAddr::UNSPECIFIED.is_unspecified());
+        assert!(MacAddr([0; 6]).is_unspecified());
+    }
+
+    #[test]
+    fn check_mac_address_is_unspecified_false() {
+        assert!(!MacAddr([1, 2, 3, 4, 5, 6]).is_unspecified());
+    }
+
+    // This test exists to ensure that the `TryFrom<usize>` and `FromStr` traits are implemented
+    // on `Uid`, `Gid` and `Pid`.
+    #[allow(clippy::unnecessary_fallible_conversions)]
+    #[test]
+    fn check_uid_gid_from_impls() {
+        use std::convert::TryFrom;
+        use std::str::FromStr;
+
+        #[cfg(not(windows))]
+        {
+            assert!(crate::Uid::try_from(0usize).is_ok());
+            assert!(crate::Uid::from_str("0").is_ok());
+        }
+        #[cfg(windows)]
+        {
+            assert!(crate::Uid::from_str("S-1-5-18").is_ok()); // SECURITY_LOCAL_SYSTEM_RID
+            assert!(crate::Uid::from_str("0").is_err());
+        }
+
+        assert!(crate::Gid::try_from(0usize).is_ok());
+        assert!(crate::Gid::from_str("0").is_ok());
+
+        assert!(crate::Pid::try_from(0usize).is_ok());
+        // If it doesn't panic, it's fine.
+        let _ = crate::Pid::from(0);
+        assert!(crate::Pid::from_str("0").is_ok());
+    }
 }
diff --git a/src/system.rs b/src/system.rs
deleted file mode 100644
index 2230645..0000000
--- a/src/system.rs
+++ /dev/null
@@ -1,185 +0,0 @@
-// Take a look at the license at the top of the repository in the LICENSE file.
-
-// Once https://github.com/rust-lang/rfcs/blob/master/text/1422-pub-restricted.md
-// feature gets stabilized, we can move common parts in here.
-
-#[cfg(test)]
-mod tests {
-    use crate::System;
-
-    #[test]
-    fn test_refresh_system() {
-        let mut sys = System::new();
-        sys.refresh_memory();
-        sys.refresh_cpu_usage();
-        // We don't want to test on unsupported systems.
-        if crate::IS_SUPPORTED {
-            assert!(sys.total_memory() != 0);
-            assert!(sys.free_memory() != 0);
-        }
-        assert!(sys.total_memory() >= sys.free_memory());
-        assert!(sys.total_swap() >= sys.free_swap());
-    }
-
-    #[test]
-    fn test_refresh_process() {
-        let mut sys = System::new();
-        assert!(sys.processes().is_empty(), "no process should be listed!");
-        // We don't want to test on unsupported systems.
-
-        #[cfg(not(feature = "apple-sandbox"))]
-        if crate::IS_SUPPORTED {
-            assert!(
-                sys.refresh_process(crate::get_current_pid().expect("failed to get current pid")),
-                "process not listed",
-            );
-            // Ensure that the process was really added to the list!
-            assert!(sys
-                .process(crate::get_current_pid().expect("failed to get current pid"))
-                .is_some());
-        }
-    }
-
-    #[test]
-    fn test_get_process() {
-        let mut sys = System::new();
-        sys.refresh_processes();
-        let current_pid = match crate::get_current_pid() {
-            Ok(pid) => pid,
-            _ => {
-                if !crate::IS_SUPPORTED {
-                    return;
-                }
-                panic!("get_current_pid should work!");
-            }
-        };
-        if let Some(p) = sys.process(current_pid) {
-            assert!(p.memory() > 0);
-        } else {
-            #[cfg(not(feature = "apple-sandbox"))]
-            assert!(!crate::IS_SUPPORTED);
-        }
-    }
-
-    #[test]
-    fn check_if_send_and_sync() {
-        trait Foo {
-            fn foo(&self) {}
-        }
-        impl<T> Foo for T where T: Send {}
-
-        trait Bar {
-            fn bar(&self) {}
-        }
-
-        impl<T> Bar for T where T: Sync {}
-
-        let mut sys = System::new();
-        sys.refresh_processes();
-        let current_pid = match crate::get_current_pid() {
-            Ok(pid) => pid,
-            _ => {
-                if !crate::IS_SUPPORTED {
-                    return;
-                }
-                panic!("get_current_pid should work!");
-            }
-        };
-        if let Some(p) = sys.process(current_pid) {
-            p.foo(); // If this doesn't compile, it'll simply mean that the Process type
-                     // doesn't implement the Send trait.
-            p.bar(); // If this doesn't compile, it'll simply mean that the Process type
-                     // doesn't implement the Sync trait.
-        } else {
-            #[cfg(not(feature = "apple-sandbox"))]
-            assert!(!crate::IS_SUPPORTED);
-        }
-    }
-
-    #[test]
-    fn check_hostname_has_no_nuls() {
-        if let Some(hostname) = System::host_name() {
-            assert!(!hostname.contains('\u{0}'))
-        }
-    }
-
-    #[test]
-    fn check_uptime() {
-        let uptime = System::uptime();
-        if crate::IS_SUPPORTED {
-            std::thread::sleep(std::time::Duration::from_millis(1000));
-            let new_uptime = System::uptime();
-            assert!(uptime < new_uptime);
-        }
-    }
-
-    #[test]
-    fn check_boot_time() {
-        if crate::IS_SUPPORTED {
-            assert_ne!(System::boot_time(), 0);
-        }
-    }
-
-    // This test is used to ensure that the CPU usage computation isn't completely going off
-    // when refreshing it too frequently (ie, multiple times in a row in a very small interval).
-    #[test]
-    #[ignore] // This test MUST be run on its own to prevent wrong CPU usage measurements.
-    fn test_consecutive_cpu_usage_update() {
-        use crate::{Pid, ProcessRefreshKind, System};
-        use std::sync::atomic::{AtomicBool, Ordering};
-        use std::sync::Arc;
-        use std::time::Duration;
-
-        if !crate::IS_SUPPORTED {
-            return;
-        }
-
-        let mut sys = System::new_all();
-        assert!(!sys.cpus().is_empty());
-        sys.refresh_processes_specifics(ProcessRefreshKind::new().with_cpu());
-
-        let stop = Arc::new(AtomicBool::new(false));
-        // Spawning a few threads to ensure that it will actually have an impact on the CPU usage.
-        for it in 0..sys.cpus().len() / 2 + 1 {
-            let stop_c = Arc::clone(&stop);
-            std::thread::spawn(move || {
-                while !stop_c.load(Ordering::Relaxed) {
-                    if it != 0 {
-                        // The first thread runs at 100% to be sure it'll be noticeable.
-                        std::thread::sleep(Duration::from_millis(1));
-                    }
-                }
-            });
-        }
-
-        let mut pids = sys
-            .processes()
-            .iter()
-            .map(|(pid, _)| *pid)
-            .take(2)
-            .collect::<Vec<_>>();
-        let pid = std::process::id();
-        pids.push(Pid::from_u32(pid));
-        assert_eq!(pids.len(), 3);
-
-        for it in 0..3 {
-            std::thread::sleep(crate::MINIMUM_CPU_UPDATE_INTERVAL + Duration::from_millis(1));
-            for pid in &pids {
-                sys.refresh_process_specifics(*pid, ProcessRefreshKind::new().with_cpu());
-            }
-            // To ensure that Linux doesn't give too high numbers.
-            assert!(
-                sys.process(pids[2]).unwrap().cpu_usage() < sys.cpus().len() as f32 * 100.,
-                "using ALL CPU: failed at iteration {}",
-                it
-            );
-            // To ensure it's not 0 either.
-            assert!(
-                sys.process(pids[2]).unwrap().cpu_usage() > 0.,
-                "using NO CPU: failed at iteration {}",
-                it
-            );
-        }
-        stop.store(false, Ordering::Relaxed);
-    }
-}
diff --git a/src/unix/apple/system.rs b/src/unix/apple/system.rs
index c04dc07..c9bf4ff 100644
--- a/src/unix/apple/system.rs
+++ b/src/unix/apple/system.rs
@@ -5,7 +5,7 @@ use crate::sys::cpu::*;
 use crate::sys::process::*;
 use crate::sys::utils::{get_sys_value, get_sys_value_by_name};
 
-use crate::{Cpu, CpuRefreshKind, LoadAvg, Pid, Process, ProcessRefreshKind};
+use crate::{Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind};
 
 use std::cell::UnsafeCell;
 use std::collections::HashMap;
@@ -94,61 +94,65 @@ impl SystemInner {
         }
     }
 
-    pub(crate) fn refresh_memory(&mut self) {
+    pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
         let mut mib = [libc::CTL_VM as _, libc::VM_SWAPUSAGE as _];
 
         unsafe {
-            // get system values
-            // get swap info
-            let mut xs: libc::xsw_usage = mem::zeroed::<libc::xsw_usage>();
-            if get_sys_value(
-                mem::size_of::<libc::xsw_usage>(),
-                &mut xs as *mut _ as *mut c_void,
-                &mut mib,
-            ) {
-                self.swap_total = xs.xsu_total;
-                self.swap_free = xs.xsu_avail;
-            }
-            mib[0] = libc::CTL_HW as _;
-            mib[1] = libc::HW_MEMSIZE as _;
-            // get ram info
-            if self.mem_total < 1 {
-                get_sys_value(
-                    mem::size_of::<u64>(),
-                    &mut self.mem_total as *mut u64 as *mut c_void,
+            if refresh_kind.swap() {
+                // get system values
+                // get swap info
+                let mut xs: libc::xsw_usage = mem::zeroed::<libc::xsw_usage>();
+                if get_sys_value(
+                    mem::size_of::<libc::xsw_usage>(),
+                    &mut xs as *mut _ as *mut c_void,
                     &mut mib,
-                );
+                ) {
+                    self.swap_total = xs.xsu_total;
+                    self.swap_free = xs.xsu_avail;
+                }
             }
-            let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _;
-            let mut stat = mem::zeroed::<vm_statistics64>();
-            if host_statistics64(
-                self.port,
-                libc::HOST_VM_INFO64,
-                &mut stat as *mut vm_statistics64 as *mut _,
-                &mut count,
-            ) == libc::KERN_SUCCESS
-            {
-                // From the apple documentation:
-                //
-                // /*
-                //  * NB: speculative pages are already accounted for in "free_count",
-                //  * so "speculative_count" is the number of "free" pages that are
-                //  * used to hold data that was read speculatively from disk but
-                //  * haven't actually been used by anyone so far.
-                //  */
-                self.mem_available = u64::from(stat.free_count)
-                    .saturating_add(u64::from(stat.inactive_count))
-                    .saturating_add(u64::from(stat.purgeable_count))
-                    .saturating_sub(u64::from(stat.compressor_page_count))
-                    .saturating_mul(self.page_size_b);
-                self.mem_used = u64::from(stat.active_count)
-                    .saturating_add(u64::from(stat.wire_count))
-                    .saturating_add(u64::from(stat.compressor_page_count))
-                    .saturating_add(u64::from(stat.speculative_count))
-                    .saturating_mul(self.page_size_b);
-                self.mem_free = u64::from(stat.free_count)
-                    .saturating_sub(u64::from(stat.speculative_count))
-                    .saturating_mul(self.page_size_b);
+            if refresh_kind.ram() {
+                mib[0] = libc::CTL_HW as _;
+                mib[1] = libc::HW_MEMSIZE as _;
+                // get ram info
+                if self.mem_total < 1 {
+                    get_sys_value(
+                        mem::size_of::<u64>(),
+                        &mut self.mem_total as *mut u64 as *mut c_void,
+                        &mut mib,
+                    );
+                }
+                let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _;
+                let mut stat = mem::zeroed::<vm_statistics64>();
+                if host_statistics64(
+                    self.port,
+                    libc::HOST_VM_INFO64,
+                    &mut stat as *mut vm_statistics64 as *mut _,
+                    &mut count,
+                ) == libc::KERN_SUCCESS
+                {
+                    // From the apple documentation:
+                    //
+                    // /*
+                    //  * NB: speculative pages are already accounted for in "free_count",
+                    //  * so "speculative_count" is the number of "free" pages that are
+                    //  * used to hold data that was read speculatively from disk but
+                    //  * haven't actually been used by anyone so far.
+                    //  */
+                    self.mem_available = u64::from(stat.free_count)
+                        .saturating_add(u64::from(stat.inactive_count))
+                        .saturating_add(u64::from(stat.purgeable_count))
+                        .saturating_sub(u64::from(stat.compressor_page_count))
+                        .saturating_mul(self.page_size_b);
+                    self.mem_used = u64::from(stat.active_count)
+                        .saturating_add(u64::from(stat.wire_count))
+                        .saturating_add(u64::from(stat.compressor_page_count))
+                        .saturating_add(u64::from(stat.speculative_count))
+                        .saturating_mul(self.page_size_b);
+                    self.mem_free = u64::from(stat.free_count)
+                        .saturating_sub(u64::from(stat.speculative_count))
+                        .saturating_mul(self.page_size_b);
+                }
             }
         }
     }
diff --git a/src/unix/freebsd/system.rs b/src/unix/freebsd/system.rs
index d8fc233..8b8d66e 100644
--- a/src/unix/freebsd/system.rs
+++ b/src/unix/freebsd/system.rs
@@ -1,6 +1,8 @@
 // Take a look at the license at the top of the repository in the LICENSE file.
 
-use crate::{Cpu, CpuRefreshKind, LoadAvg, Pid, Process, ProcessInner, ProcessRefreshKind};
+use crate::{
+    Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessInner, ProcessRefreshKind,
+};
 
 use std::cell::UnsafeCell;
 use std::collections::HashMap;
@@ -43,15 +45,19 @@ impl SystemInner {
         }
     }
 
-    pub(crate) fn refresh_memory(&mut self) {
-        if self.mem_total == 0 {
-            self.mem_total = self.system_info.get_total_memory();
+    pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
+        if refresh_kind.ram() {
+            if self.mem_total == 0 {
+                self.mem_total = self.system_info.get_total_memory();
+            }
+            self.mem_used = self.system_info.get_used_memory();
+            self.mem_free = self.system_info.get_free_memory();
+        }
+        if refresh_kind.swap() {
+            let (swap_used, swap_total) = self.system_info.get_swap_info();
+            self.swap_total = swap_total;
+            self.swap_used = swap_used;
         }
-        self.mem_used = self.system_info.get_used_memory();
-        self.mem_free = self.system_info.get_free_memory();
-        let (swap_used, swap_total) = self.system_info.get_swap_info();
-        self.swap_total = swap_total;
-        self.swap_used = swap_used;
     }
 
     pub(crate) fn cgroup_limits(&self) -> Option<crate::CGroupLimits> {
diff --git a/src/unix/linux/system.rs b/src/unix/linux/system.rs
index 16e3e63..734e645 100644
--- a/src/unix/linux/system.rs
+++ b/src/unix/linux/system.rs
@@ -3,7 +3,7 @@
 use crate::sys::cpu::{get_physical_core_count, CpusWrapper};
 use crate::sys::process::{_get_process_data, compute_cpu_usage, refresh_procs, unset_updated};
 use crate::sys::utils::{get_all_data, to_u64};
-use crate::{Cpu, CpuRefreshKind, LoadAvg, Pid, Process, ProcessRefreshKind};
+use crate::{Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind};
 
 use libc::{self, c_char, sysconf, _SC_CLK_TCK, _SC_HOST_NAME_MAX, _SC_PAGESIZE};
 use std::cmp::min;
@@ -187,7 +187,10 @@ impl SystemInner {
         }
     }
 
-    pub(crate) fn refresh_memory(&mut self) {
+    pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
+        if !refresh_kind.ram() && !refresh_kind.swap() {
+            return;
+        }
         let mut mem_available_found = false;
         read_table("/proc/meminfo", ':', |key, value_kib| {
             let field = match key {
diff --git a/src/unknown/system.rs b/src/unknown/system.rs
index 766038f..19aada5 100644
--- a/src/unknown/system.rs
+++ b/src/unknown/system.rs
@@ -1,6 +1,8 @@
 // Take a look at the license at the top of the repository in the LICENSE file.
 
-use crate::{Cpu, CpuInner, CpuRefreshKind, LoadAvg, Pid, Process, ProcessRefreshKind};
+use crate::{
+    Cpu, CpuInner, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind,
+};
 
 use std::collections::HashMap;
 
@@ -19,7 +21,7 @@ impl SystemInner {
         }
     }
 
-    pub(crate) fn refresh_memory(&mut self) {}
+    pub(crate) fn refresh_memory_specifics(&mut self, _refresh_kind: MemoryRefreshKind) {}
 
     pub(crate) fn cgroup_limits(&self) -> Option<crate::CGroupLimits> {
         None
diff --git a/src/windows/system.rs b/src/windows/system.rs
index 50f3fd1..ea16ce9 100644
--- a/src/windows/system.rs
+++ b/src/windows/system.rs
@@ -1,6 +1,6 @@
 // Take a look at the license at the top of the repository in the LICENSE file.
 
-use crate::{Cpu, CpuRefreshKind, LoadAvg, Pid, ProcessRefreshKind};
+use crate::{Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, ProcessRefreshKind};
 
 use crate::sys::cpu::*;
 use crate::sys::process::get_start_time;
@@ -136,29 +136,36 @@ impl SystemInner {
         }
     }
 
-    pub(crate) fn refresh_memory(&mut self) {
+    pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
         unsafe {
-            let mut mem_info: MEMORYSTATUSEX = zeroed();
-            mem_info.dwLength = size_of::<MEMORYSTATUSEX>() as u32;
-            let _err = GlobalMemoryStatusEx(&mut mem_info);
-            self.mem_total = mem_info.ullTotalPhys as _;
-            self.mem_available = mem_info.ullAvailPhys as _;
-            let mut perf_info: PERFORMANCE_INFORMATION = zeroed();
-            if K32GetPerformanceInfo(&mut perf_info, size_of::<PERFORMANCE_INFORMATION>() as u32)
+            if refresh_kind.ram() {
+                let mut mem_info: MEMORYSTATUSEX = zeroed();
+                mem_info.dwLength = size_of::<MEMORYSTATUSEX>() as u32;
+                let _err = GlobalMemoryStatusEx(&mut mem_info);
+                self.mem_total = mem_info.ullTotalPhys as _;
+                self.mem_available = mem_info.ullAvailPhys as _;
+            }
+            if refresh_kind.swap() {
+                let mut perf_info: PERFORMANCE_INFORMATION = zeroed();
+                if K32GetPerformanceInfo(
+                    &mut perf_info,
+                    size_of::<PERFORMANCE_INFORMATION>() as u32,
+                )
                 .as_bool()
-            {
-                let swap_total = perf_info.PageSize.saturating_mul(
-                    perf_info
-                        .CommitLimit
-                        .saturating_sub(perf_info.PhysicalTotal),
-                );
-                let swap_used = perf_info.PageSize.saturating_mul(
-                    perf_info
-                        .CommitTotal
-                        .saturating_sub(perf_info.PhysicalTotal),
-                );
-                self.swap_total = swap_total as _;
-                self.swap_used = swap_used as _;
+                {
+                    let swap_total = perf_info.PageSize.saturating_mul(
+                        perf_info
+                            .CommitLimit
+                            .saturating_sub(perf_info.PhysicalTotal),
+                    );
+                    let swap_used = perf_info.PageSize.saturating_mul(
+                        perf_info
+                            .CommitTotal
+                            .saturating_sub(perf_info.PhysicalTotal),
+                    );
+                    self.swap_total = swap_total as _;
+                    self.swap_used = swap_used as _;
+                }
             }
         }
     }


Test patch:
diff --git a/tests/system.rs b/tests/system.rs
new file mode 100644
index 0000000..1893926
--- /dev/null
+++ b/tests/system.rs
@@ -0,0 +1,233 @@
+// Take a look at the license at the top of the repository in the LICENSE file.
+
+#![allow(clippy::assertions_on_constants)]
+
+use sysinfo::System;
+
+#[test]
+fn test_refresh_system() {
+    let mut sys = System::new();
+    sys.refresh_memory();
+    sys.refresh_cpu_usage();
+    // We don't want to test on unsupported systems.
+    if sysinfo::IS_SUPPORTED {
+        assert!(sys.total_memory() != 0);
+        assert!(sys.free_memory() != 0);
+    }
+    assert!(sys.total_memory() >= sys.free_memory());
+    assert!(sys.total_swap() >= sys.free_swap());
+}
+
+#[test]
+fn test_refresh_process() {
+    let mut sys = System::new();
+    assert!(sys.processes().is_empty(), "no process should be listed!");
+    // We don't want to test on unsupported systems.
+
+    #[cfg(not(feature = "apple-sandbox"))]
+    if sysinfo::IS_SUPPORTED {
+        assert!(
+            sys.refresh_process(sysinfo::get_current_pid().expect("failed to get current pid")),
+            "process not listed",
+        );
+        // Ensure that the process was really added to the list!
+        assert!(sys
+            .process(sysinfo::get_current_pid().expect("failed to get current pid"))
+            .is_some());
+    }
+}
+
+#[test]
+fn test_get_process() {
+    let mut sys = System::new();
+    sys.refresh_processes();
+    let current_pid = match sysinfo::get_current_pid() {
+        Ok(pid) => pid,
+        _ => {
+            if !sysinfo::IS_SUPPORTED {
+                return;
+            }
+            panic!("get_current_pid should work!");
+        }
+    };
+    if let Some(p) = sys.process(current_pid) {
+        assert!(p.memory() > 0);
+    } else {
+        #[cfg(not(feature = "apple-sandbox"))]
+        assert!(!sysinfo::IS_SUPPORTED);
+    }
+}
+
+#[test]
+fn check_if_send_and_sync() {
+    trait Foo {
+        fn foo(&self) {}
+    }
+    impl<T> Foo for T where T: Send {}
+
+    trait Bar {
+        fn bar(&self) {}
+    }
+
+    impl<T> Bar for T where T: Sync {}
+
+    let mut sys = System::new();
+    sys.refresh_processes();
+    let current_pid = match sysinfo::get_current_pid() {
+        Ok(pid) => pid,
+        _ => {
+            if !sysinfo::IS_SUPPORTED {
+                return;
+            }
+            panic!("get_current_pid should work!");
+        }
+    };
+    if let Some(p) = sys.process(current_pid) {
+        p.foo(); // If this doesn't compile, it'll simply mean that the Process type
+                 // doesn't implement the Send trait.
+        p.bar(); // If this doesn't compile, it'll simply mean that the Process type
+                 // doesn't implement the Sync trait.
+    } else {
+        #[cfg(not(feature = "apple-sandbox"))]
+        assert!(!sysinfo::IS_SUPPORTED);
+    }
+}
+
+#[test]
+fn check_hostname_has_no_nuls() {
+    if let Some(hostname) = System::host_name() {
+        assert!(!hostname.contains('\u{0}'))
+    }
+}
+
+#[test]
+fn check_uptime() {
+    let uptime = System::uptime();
+    if sysinfo::IS_SUPPORTED {
+        std::thread::sleep(std::time::Duration::from_millis(1000));
+        let new_uptime = System::uptime();
+        assert!(uptime < new_uptime);
+    }
+}
+
+#[test]
+fn check_boot_time() {
+    if sysinfo::IS_SUPPORTED {
+        assert_ne!(System::boot_time(), 0);
+    }
+}
+
+// This test is used to ensure that the CPU usage computation isn't completely going off
+// when refreshing it too frequently (ie, multiple times in a row in a very small interval).
+#[test]
+#[ignore] // This test MUST be run on its own to prevent wrong CPU usage measurements.
+fn test_consecutive_cpu_usage_update() {
+    use std::sync::atomic::{AtomicBool, Ordering};
+    use std::sync::Arc;
+    use std::time::Duration;
+    use sysinfo::{Pid, ProcessRefreshKind, System};
+
+    if !sysinfo::IS_SUPPORTED {
+        return;
+    }
+
+    let mut sys = System::new_all();
+    assert!(!sys.cpus().is_empty());
+    sys.refresh_processes_specifics(ProcessRefreshKind::new().with_cpu());
+
+    let stop = Arc::new(AtomicBool::new(false));
+    // Spawning a few threads to ensure that it will actually have an impact on the CPU usage.
+    for it in 0..sys.cpus().len() / 2 + 1 {
+        let stop_c = Arc::clone(&stop);
+        std::thread::spawn(move || {
+            while !stop_c.load(Ordering::Relaxed) {
+                if it != 0 {
+                    // The first thread runs at 100% to be sure it'll be noticeable.
+                    std::thread::sleep(Duration::from_millis(1));
+                }
+            }
+        });
+    }
+
+    let mut pids = sys
+        .processes()
+        .iter()
+        .map(|(pid, _)| *pid)
+        .take(2)
+        .collect::<Vec<_>>();
+    let pid = std::process::id();
+    pids.push(Pid::from_u32(pid));
+    assert_eq!(pids.len(), 3);
+
+    for it in 0..3 {
+        std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL + Duration::from_millis(1));
+        for pid in &pids {
+            sys.refresh_process_specifics(*pid, ProcessRefreshKind::new().with_cpu());
+        }
+        // To ensure that Linux doesn't give too high numbers.
+        assert!(
+            sys.process(pids[2]).unwrap().cpu_usage() < sys.cpus().len() as f32 * 100.,
+            "using ALL CPU: failed at iteration {}",
+            it
+        );
+        // To ensure it's not 0 either.
+        assert!(
+            sys.process(pids[2]).unwrap().cpu_usage() > 0.,
+            "using NO CPU: failed at iteration {}",
+            it
+        );
+    }
+    stop.store(false, Ordering::Relaxed);
+}
+
+#[test]
+fn test_refresh_memory() {
+    if !sysinfo::IS_SUPPORTED {
+        return;
+    }
+    // On linux, since it's the same file, memory information are always retrieved.
+    let is_linux = cfg!(any(target_os = "linux", target_os = "android"));
+    let mut s = System::new();
+    assert_eq!(s.total_memory(), 0);
+    assert_eq!(s.free_memory(), 0);
+
+    s.refresh_memory_specifics(sysinfo::MemoryRefreshKind::new().with_ram());
+    assert_ne!(s.total_memory(), 0);
+    assert_ne!(s.free_memory(), 0);
+
+    if is_linux {
+        assert_ne!(s.total_swap(), 0);
+        assert_ne!(s.free_swap(), 0);
+    } else {
+        assert_eq!(s.total_swap(), 0);
+        assert_eq!(s.free_swap(), 0);
+    }
+
+    let mut s = System::new();
+    assert_eq!(s.total_swap(), 0);
+    assert_eq!(s.free_swap(), 0);
+
+    if std::env::var("APPLE_CI").is_ok() {
+        // Apparently there is no swap for macOS in CIs so can't run futher than this point.
+        return;
+    }
+
+    s.refresh_memory_specifics(sysinfo::MemoryRefreshKind::new().with_swap());
+    assert_ne!(s.total_swap(), 0);
+    assert_ne!(s.free_swap(), 0);
+
+    if is_linux {
+        assert_ne!(s.total_memory(), 0);
+        assert_ne!(s.free_memory(), 0);
+    } else {
+        assert_eq!(s.total_memory(), 0);
+        assert_eq!(s.free_memory(), 0);
+    }
+
+    let mut s = System::new();
+    s.refresh_memory();
+    assert_ne!(s.total_swap(), 0);
+    assert_ne!(s.free_swap(), 0);
+    assert_ne!(s.total_memory(), 0);
+    assert_ne!(s.free_memory(), 0);
+}