diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/num_cpus/src | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/num_cpus/src')
-rw-r--r-- | third_party/rust/num_cpus/src/lib.rs | 459 | ||||
-rw-r--r-- | third_party/rust/num_cpus/src/linux.rs | 595 |
2 files changed, 1054 insertions, 0 deletions
diff --git a/third_party/rust/num_cpus/src/lib.rs b/third_party/rust/num_cpus/src/lib.rs new file mode 100644 index 0000000000..20474af4b3 --- /dev/null +++ b/third_party/rust/num_cpus/src/lib.rs @@ -0,0 +1,459 @@ +//! A crate with utilities to determine the number of CPUs available on the +//! current system. +//! +//! Sometimes the CPU will exaggerate the number of CPUs it contains, because it can use +//! [processor tricks] to deliver increased performance when there are more threads. This +//! crate provides methods to get both the logical and physical numbers of cores. +//! +//! This information can be used as a guide to how many tasks can be run in parallel. +//! There are many properties of the system architecture that will affect parallelism, +//! for example memory access speeds (for all the caches and RAM) and the physical +//! architecture of the processor, so the number of CPUs should be used as a rough guide +//! only. +//! +//! +//! ## Examples +//! +//! Fetch the number of logical CPUs. +//! +//! ``` +//! let cpus = num_cpus::get(); +//! ``` +//! +//! See [`rayon::Threadpool`] for an example of where the number of CPUs could be +//! used when setting up parallel jobs (Where the threadpool example uses a fixed +//! number 8, it could use the number of CPUs). +//! +//! [processor tricks]: https://en.wikipedia.org/wiki/Simultaneous_multithreading +//! [`rayon::ThreadPool`]: https://docs.rs/rayon/1.*/rayon/struct.ThreadPool.html +#![cfg_attr(test, deny(warnings))] +#![deny(missing_docs)] +#![allow(non_snake_case)] + +#[cfg(not(windows))] +extern crate libc; + +#[cfg(target_os = "hermit")] +extern crate hermit_abi; + +#[cfg(target_os = "linux")] +mod linux; +#[cfg(target_os = "linux")] +use linux::{get_num_cpus, get_num_physical_cpus}; + +/// Returns the number of available CPUs of the current system. +/// +/// This function will get the number of logical cores. Sometimes this is different from the number +/// of physical cores (See [Simultaneous multithreading on Wikipedia][smt]). +/// +/// This will always return at least `1`. +/// +/// # Examples +/// +/// ``` +/// let cpus = num_cpus::get(); +/// if cpus > 1 { +/// println!("We are on a multicore system with {} CPUs", cpus); +/// } else { +/// println!("We are on a single core system"); +/// } +/// ``` +/// +/// # Note +/// +/// This will check [sched affinity] on Linux, showing a lower number of CPUs if the current +/// thread does not have access to all the computer's CPUs. +/// +/// This will also check [cgroups], frequently used in containers to constrain CPU usage. +/// +/// [smt]: https://en.wikipedia.org/wiki/Simultaneous_multithreading +/// [sched affinity]: http://www.gnu.org/software/libc/manual/html_node/CPU-Affinity.html +/// [cgroups]: https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt +#[inline] +pub fn get() -> usize { + get_num_cpus() +} + +/// Returns the number of physical cores of the current system. +/// +/// This will always return at least `1`. +/// +/// # Note +/// +/// Physical count is supported only on Linux, mac OS and Windows platforms. +/// On other platforms, or if the physical count fails on supported platforms, +/// this function returns the same as [`get()`], which is the number of logical +/// CPUS. +/// +/// # Examples +/// +/// ``` +/// let logical_cpus = num_cpus::get(); +/// let physical_cpus = num_cpus::get_physical(); +/// if logical_cpus > physical_cpus { +/// println!("We have simultaneous multithreading with about {:.2} \ +/// logical cores to 1 physical core.", +/// (logical_cpus as f64) / (physical_cpus as f64)); +/// } else if logical_cpus == physical_cpus { +/// println!("Either we don't have simultaneous multithreading, or our \ +/// system doesn't support getting the number of physical CPUs."); +/// } else { +/// println!("We have less logical CPUs than physical CPUs, maybe we only have access to \ +/// some of the CPUs on our system."); +/// } +/// ``` +/// +/// [`get()`]: fn.get.html +#[inline] +pub fn get_physical() -> usize { + get_num_physical_cpus() +} + + +#[cfg(not(any(target_os = "linux", target_os = "windows", target_os="macos", target_os="openbsd")))] +#[inline] +fn get_num_physical_cpus() -> usize { + // Not implemented, fall back + get_num_cpus() +} + +#[cfg(target_os = "windows")] +fn get_num_physical_cpus() -> usize { + match get_num_physical_cpus_windows() { + Some(num) => num, + None => get_num_cpus() + } +} + +#[cfg(target_os = "windows")] +fn get_num_physical_cpus_windows() -> Option<usize> { + // Inspired by https://msdn.microsoft.com/en-us/library/ms683194 + + use std::ptr; + use std::mem; + + #[allow(non_upper_case_globals)] + const RelationProcessorCore: u32 = 0; + + #[repr(C)] + #[allow(non_camel_case_types)] + struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION { + mask: usize, + relationship: u32, + _unused: [u64; 2] + } + + extern "system" { + fn GetLogicalProcessorInformation( + info: *mut SYSTEM_LOGICAL_PROCESSOR_INFORMATION, + length: &mut u32 + ) -> u32; + } + + // First we need to determine how much space to reserve. + + // The required size of the buffer, in bytes. + let mut needed_size = 0; + + unsafe { + GetLogicalProcessorInformation(ptr::null_mut(), &mut needed_size); + } + + let struct_size = mem::size_of::<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>() as u32; + + // Could be 0, or some other bogus size. + if needed_size == 0 || needed_size < struct_size || needed_size % struct_size != 0 { + return None; + } + + let count = needed_size / struct_size; + + // Allocate some memory where we will store the processor info. + let mut buf = Vec::with_capacity(count as usize); + + let result; + + unsafe { + result = GetLogicalProcessorInformation(buf.as_mut_ptr(), &mut needed_size); + } + + // Failed for any reason. + if result == 0 { + return None; + } + + let count = needed_size / struct_size; + + unsafe { + buf.set_len(count as usize); + } + + let phys_proc_count = buf.iter() + // Only interested in processor packages (physical processors.) + .filter(|proc_info| proc_info.relationship == RelationProcessorCore) + .count(); + + if phys_proc_count == 0 { + None + } else { + Some(phys_proc_count) + } +} + +#[cfg(windows)] +fn get_num_cpus() -> usize { + #[repr(C)] + struct SYSTEM_INFO { + wProcessorArchitecture: u16, + wReserved: u16, + dwPageSize: u32, + lpMinimumApplicationAddress: *mut u8, + lpMaximumApplicationAddress: *mut u8, + dwActiveProcessorMask: *mut u8, + dwNumberOfProcessors: u32, + dwProcessorType: u32, + dwAllocationGranularity: u32, + wProcessorLevel: u16, + wProcessorRevision: u16, + } + + extern "system" { + fn GetSystemInfo(lpSystemInfo: *mut SYSTEM_INFO); + } + + unsafe { + let mut sysinfo: SYSTEM_INFO = std::mem::zeroed(); + GetSystemInfo(&mut sysinfo); + sysinfo.dwNumberOfProcessors as usize + } +} + +#[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd"))] +fn get_num_cpus() -> usize { + use std::ptr; + + let mut cpus: libc::c_uint = 0; + let mut cpus_size = std::mem::size_of_val(&cpus); + + unsafe { + cpus = libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as libc::c_uint; + } + if cpus < 1 { + let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0]; + unsafe { + libc::sysctl(mib.as_mut_ptr(), + 2, + &mut cpus as *mut _ as *mut _, + &mut cpus_size as *mut _ as *mut _, + ptr::null_mut(), + 0); + } + if cpus < 1 { + cpus = 1; + } + } + cpus as usize +} + +#[cfg(target_os = "openbsd")] +fn get_num_cpus() -> usize { + use std::ptr; + + let mut cpus: libc::c_uint = 0; + let mut cpus_size = std::mem::size_of_val(&cpus); + let mut mib = [libc::CTL_HW, libc::HW_NCPUONLINE, 0, 0]; + let rc: libc::c_int; + + unsafe { + rc = libc::sysctl(mib.as_mut_ptr(), + 2, + &mut cpus as *mut _ as *mut _, + &mut cpus_size as *mut _ as *mut _, + ptr::null_mut(), + 0); + } + if rc < 0 { + cpus = 1; + } + cpus as usize +} + +#[cfg(target_os = "openbsd")] +fn get_num_physical_cpus() -> usize { + use std::ptr; + + let mut cpus: libc::c_uint = 0; + let mut cpus_size = std::mem::size_of_val(&cpus); + let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0]; + let rc: libc::c_int; + + unsafe { + rc = libc::sysctl(mib.as_mut_ptr(), + 2, + &mut cpus as *mut _ as *mut _, + &mut cpus_size as *mut _ as *mut _, + ptr::null_mut(), + 0); + } + if rc < 0 { + cpus = 1; + } + cpus as usize +} + + +#[cfg(target_os = "macos")] +fn get_num_physical_cpus() -> usize { + use std::ffi::CStr; + use std::ptr; + + let mut cpus: i32 = 0; + let mut cpus_size = std::mem::size_of_val(&cpus); + + let sysctl_name = CStr::from_bytes_with_nul(b"hw.physicalcpu\0") + .expect("byte literal is missing NUL"); + + unsafe { + if 0 != libc::sysctlbyname(sysctl_name.as_ptr(), + &mut cpus as *mut _ as *mut _, + &mut cpus_size as *mut _ as *mut _, + ptr::null_mut(), + 0) { + return get_num_cpus(); + } + } + cpus as usize +} + +#[cfg(any( + target_os = "nacl", + target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "solaris", + target_os = "illumos", + target_os = "fuchsia") +)] +fn get_num_cpus() -> usize { + // On ARM targets, processors could be turned off to save power. + // Use `_SC_NPROCESSORS_CONF` to get the real number. + #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] + const CONF_NAME: libc::c_int = libc::_SC_NPROCESSORS_CONF; + #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))] + const CONF_NAME: libc::c_int = libc::_SC_NPROCESSORS_ONLN; + + let cpus = unsafe { libc::sysconf(CONF_NAME) }; + if cpus < 1 { + 1 + } else { + cpus as usize + } +} + +#[cfg(target_os = "haiku")] +fn get_num_cpus() -> usize { + use std::mem; + + #[allow(non_camel_case_types)] + type bigtime_t = i64; + #[allow(non_camel_case_types)] + type status_t = i32; + + #[repr(C)] + pub struct system_info { + pub boot_time: bigtime_t, + pub cpu_count: u32, + pub max_pages: u64, + pub used_pages: u64, + pub cached_pages: u64, + pub block_cache_pages: u64, + pub ignored_pages: u64, + pub needed_memory: u64, + pub free_memory: u64, + pub max_swap_pages: u64, + pub free_swap_pages: u64, + pub page_faults: u32, + pub max_sems: u32, + pub used_sems: u32, + pub max_ports: u32, + pub used_ports: u32, + pub max_threads: u32, + pub used_threads: u32, + pub max_teams: u32, + pub used_teams: u32, + pub kernel_name: [::std::os::raw::c_char; 256usize], + pub kernel_build_date: [::std::os::raw::c_char; 32usize], + pub kernel_build_time: [::std::os::raw::c_char; 32usize], + pub kernel_version: i64, + pub abi: u32, + } + + extern { + fn get_system_info(info: *mut system_info) -> status_t; + } + + let mut info: system_info = unsafe { mem::zeroed() }; + let status = unsafe { get_system_info(&mut info as *mut _) }; + if status == 0 { + info.cpu_count as usize + } else { + 1 + } +} + +#[cfg(target_os = "hermit")] +fn get_num_cpus() -> usize { + unsafe { hermit_abi::get_processor_count() } +} + +#[cfg(not(any( + target_os = "nacl", + target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "solaris", + target_os = "illumos", + target_os = "fuchsia", + target_os = "linux", + target_os = "openbsd", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "haiku", + target_os = "hermit", + windows, +)))] +fn get_num_cpus() -> usize { + 1 +} + +#[cfg(test)] +mod tests { + fn env_var(name: &'static str) -> Option<usize> { + ::std::env::var(name).ok().map(|val| val.parse().unwrap()) + } + + #[test] + fn test_get() { + let num = super::get(); + if let Some(n) = env_var("NUM_CPUS_TEST_GET") { + assert_eq!(num, n); + } else { + assert!(num > 0); + assert!(num < 236_451); + } + } + + #[test] + fn test_get_physical() { + let num = super::get_physical(); + if let Some(n) = env_var("NUM_CPUS_TEST_GET_PHYSICAL") { + assert_eq!(num, n); + } else { + assert!(num > 0); + assert!(num < 236_451); + } + } +} diff --git a/third_party/rust/num_cpus/src/linux.rs b/third_party/rust/num_cpus/src/linux.rs new file mode 100644 index 0000000000..295c925fba --- /dev/null +++ b/third_party/rust/num_cpus/src/linux.rs @@ -0,0 +1,595 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::{BufRead, BufReader, Read}; +use std::mem; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Once; + +use libc; + +macro_rules! debug { + ($($args:expr),*) => ({ + if false { + //if true { + println!($($args),*); + } + }); +} + +macro_rules! some { + ($e:expr) => {{ + match $e { + Some(v) => v, + None => { + debug!("NONE: {:?}", stringify!($e)); + return None; + } + } + }}; +} + +pub fn get_num_cpus() -> usize { + match cgroups_num_cpus() { + Some(n) => n, + None => logical_cpus(), + } +} + +fn logical_cpus() -> usize { + let mut set: libc::cpu_set_t = unsafe { mem::zeroed() }; + if unsafe { libc::sched_getaffinity(0, mem::size_of::<libc::cpu_set_t>(), &mut set) } == 0 { + let mut count: u32 = 0; + for i in 0..libc::CPU_SETSIZE as usize { + if unsafe { libc::CPU_ISSET(i, &set) } { + count += 1 + } + } + count as usize + } else { + let cpus = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) }; + if cpus < 1 { + 1 + } else { + cpus as usize + } + } +} + +pub fn get_num_physical_cpus() -> usize { + let file = match File::open("/proc/cpuinfo") { + Ok(val) => val, + Err(_) => return get_num_cpus(), + }; + let reader = BufReader::new(file); + let mut map = HashMap::new(); + let mut physid: u32 = 0; + let mut cores: usize = 0; + let mut chgcount = 0; + for line in reader.lines().filter_map(|result| result.ok()) { + let mut it = line.split(':'); + let (key, value) = match (it.next(), it.next()) { + (Some(key), Some(value)) => (key.trim(), value.trim()), + _ => continue, + }; + if key == "physical id" { + match value.parse() { + Ok(val) => physid = val, + Err(_) => break, + }; + chgcount += 1; + } + if key == "cpu cores" { + match value.parse() { + Ok(val) => cores = val, + Err(_) => break, + }; + chgcount += 1; + } + if chgcount == 2 { + map.insert(physid, cores); + chgcount = 0; + } + } + let count = map.into_iter().fold(0, |acc, (_, cores)| acc + cores); + + if count == 0 { + get_num_cpus() + } else { + count + } +} + +/// Cached CPUs calculated from cgroups. +/// +/// If 0, check logical cpus. +// Allow deprecation warnings, we want to work on older rustc +#[allow(warnings)] +static CGROUPS_CPUS: AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT; + +fn cgroups_num_cpus() -> Option<usize> { + #[allow(warnings)] + static ONCE: Once = ::std::sync::ONCE_INIT; + + ONCE.call_once(init_cgroups); + + let cpus = CGROUPS_CPUS.load(Ordering::Acquire); + + if cpus > 0 { + Some(cpus) + } else { + None + } +} + +fn init_cgroups() { + // Should only be called once + debug_assert!(CGROUPS_CPUS.load(Ordering::SeqCst) == 0); + + // Fails in Miri by default (cannot open files), and Miri does not have parallelism anyway. + if cfg!(miri) { + return; + } + + if let Some(quota) = load_cgroups("/proc/self/cgroup", "/proc/self/mountinfo") { + if quota == 0 { + return; + } + + let logical = logical_cpus(); + let count = ::std::cmp::min(quota, logical); + + CGROUPS_CPUS.store(count, Ordering::SeqCst); + } +} + +fn load_cgroups<P1, P2>(cgroup_proc: P1, mountinfo_proc: P2) -> Option<usize> +where + P1: AsRef<Path>, + P2: AsRef<Path>, +{ + let subsys = some!(Subsys::load_cpu(cgroup_proc)); + let mntinfo = some!(MountInfo::load_cpu(mountinfo_proc, subsys.version)); + let cgroup = some!(Cgroup::translate(mntinfo, subsys)); + cgroup.cpu_quota() +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum CgroupVersion { + V1, + V2, +} + +struct Cgroup { + version: CgroupVersion, + base: PathBuf, +} + +struct MountInfo { + version: CgroupVersion, + root: String, + mount_point: String, +} + +struct Subsys { + version: CgroupVersion, + base: String, +} + +impl Cgroup { + fn new(version: CgroupVersion, dir: PathBuf) -> Cgroup { + Cgroup { version: version, base: dir } + } + + fn translate(mntinfo: MountInfo, subsys: Subsys) -> Option<Cgroup> { + // Translate the subsystem directory via the host paths. + debug!( + "subsys = {:?}; root = {:?}; mount_point = {:?}", + subsys.base, mntinfo.root, mntinfo.mount_point + ); + + let rel_from_root = some!(Path::new(&subsys.base).strip_prefix(&mntinfo.root).ok()); + + debug!("rel_from_root: {:?}", rel_from_root); + + // join(mp.MountPoint, relPath) + let mut path = PathBuf::from(mntinfo.mount_point); + path.push(rel_from_root); + Some(Cgroup::new(mntinfo.version, path)) + } + + fn cpu_quota(&self) -> Option<usize> { + let (quota_us, period_us) = match self.version { + CgroupVersion::V1 => (some!(self.quota_us()), some!(self.period_us())), + CgroupVersion::V2 => some!(self.max()), + }; + + // protect against dividing by zero + if period_us == 0 { + return None; + } + + // Ceil the division, since we want to be able to saturate + // the available CPUs, and flooring would leave a CPU un-utilized. + + Some((quota_us as f64 / period_us as f64).ceil() as usize) + } + + fn quota_us(&self) -> Option<usize> { + self.param("cpu.cfs_quota_us") + } + + fn period_us(&self) -> Option<usize> { + self.param("cpu.cfs_period_us") + } + + fn max(&self) -> Option<(usize, usize)> { + let max = some!(self.raw_param("cpu.max")); + let mut max = some!(max.lines().next()).split(' '); + + let quota = some!(max.next().and_then(|quota| quota.parse().ok())); + let period = some!(max.next().and_then(|period| period.parse().ok())); + + Some((quota, period)) + } + + fn param(&self, param: &str) -> Option<usize> { + let buf = some!(self.raw_param(param)); + + buf.trim().parse().ok() + } + + fn raw_param(&self, param: &str) -> Option<String> { + let mut file = some!(File::open(self.base.join(param)).ok()); + + let mut buf = String::new(); + some!(file.read_to_string(&mut buf).ok()); + + Some(buf) + } +} + +impl MountInfo { + fn load_cpu<P: AsRef<Path>>(proc_path: P, version: CgroupVersion) -> Option<MountInfo> { + let file = some!(File::open(proc_path).ok()); + let file = BufReader::new(file); + + file.lines() + .filter_map(|result| result.ok()) + .filter_map(MountInfo::parse_line) + .find(|mount_info| mount_info.version == version) + } + + fn parse_line(line: String) -> Option<MountInfo> { + let mut fields = line.split(' '); + + // 7 5 0:6 </> /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct + let mnt_root = some!(fields.nth(3)); + // 7 5 0:6 / </sys/fs/cgroup/cpu,cpuacct> rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct + let mnt_point = some!(fields.next()); + + // Ignore all fields until the separator(-). + // Note: there could be zero or more optional fields before hyphen. + // See: https://man7.org/linux/man-pages/man5/proc.5.html + // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 <-> cgroup cgroup rw,cpu,cpuacct + // Note: we cannot use `?` here because we need to support Rust 1.13. + match fields.find(|&s| s == "-") { + Some(_) => {} + None => return None, + }; + + // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - <cgroup> cgroup rw,cpu,cpuacct + let version = match fields.next() { + Some("cgroup") => CgroupVersion::V1, + Some("cgroup2") => CgroupVersion::V2, + _ => return None, + }; + + // cgroups2 only has a single mount point + if version == CgroupVersion::V1 { + // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup <rw,cpu,cpuacct> + let super_opts = some!(fields.nth(1)); + + // We only care about the 'cpu' option + if !super_opts.split(',').any(|opt| opt == "cpu") { + return None; + } + } + + Some(MountInfo { + version: version, + root: mnt_root.to_owned(), + mount_point: mnt_point.to_owned(), + }) + } +} + +impl Subsys { + fn load_cpu<P: AsRef<Path>>(proc_path: P) -> Option<Subsys> { + let file = some!(File::open(proc_path).ok()); + let file = BufReader::new(file); + + file.lines() + .filter_map(|result| result.ok()) + .filter_map(Subsys::parse_line) + .fold(None, |previous, line| { + // already-found v1 trumps v2 since it explicitly specifies its controllers + if previous.is_some() && line.version == CgroupVersion::V2 { + return previous; + } + + Some(line) + }) + } + + fn parse_line(line: String) -> Option<Subsys> { + // Example format: + // 11:cpu,cpuacct:/ + let mut fields = line.split(':'); + + let sub_systems = some!(fields.nth(1)); + + let version = if sub_systems.is_empty() { + CgroupVersion::V2 + } else { + CgroupVersion::V1 + }; + + if version == CgroupVersion::V1 && !sub_systems.split(',').any(|sub| sub == "cpu") { + return None; + } + + fields.next().map(|path| Subsys { + version: version, + base: path.to_owned(), + }) + } +} + +#[cfg(test)] +mod tests { + mod v1 { + use super::super::{Cgroup, CgroupVersion, MountInfo, Subsys}; + use std::path::{Path, PathBuf}; + + // `static_in_const` feature is not stable in Rust 1.13. + static FIXTURES_PROC: &'static str = "fixtures/cgroups/proc/cgroups"; + + static FIXTURES_CGROUPS: &'static str = "fixtures/cgroups/cgroups"; + + macro_rules! join { + ($base:expr, $($path:expr),+) => ({ + Path::new($base) + $(.join($path))+ + }) + } + + #[test] + fn test_load_mountinfo() { + // test only one optional fields + let path = join!(FIXTURES_PROC, "mountinfo"); + + let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V1).unwrap(); + + assert_eq!(mnt_info.root, "/"); + assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct"); + + // test zero optional field + let path = join!(FIXTURES_PROC, "mountinfo_zero_opt"); + + let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V1).unwrap(); + + assert_eq!(mnt_info.root, "/"); + assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct"); + + // test multi optional fields + let path = join!(FIXTURES_PROC, "mountinfo_multi_opt"); + + let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V1).unwrap(); + + assert_eq!(mnt_info.root, "/"); + assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct"); + } + + #[test] + fn test_load_subsys() { + let path = join!(FIXTURES_PROC, "cgroup"); + + let subsys = Subsys::load_cpu(path).unwrap(); + + assert_eq!(subsys.base, "/"); + assert_eq!(subsys.version, CgroupVersion::V1); + } + + #[test] + fn test_cgroup_mount() { + let cases = &[ + ("/", "/sys/fs/cgroup/cpu", "/", Some("/sys/fs/cgroup/cpu")), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd", + Some("/sys/fs/cgroup/cpu"), + ), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd/", + Some("/sys/fs/cgroup/cpu"), + ), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd/large", + Some("/sys/fs/cgroup/cpu/large"), + ), + // fails + ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/", None), + ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/docker", None), + ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/elsewhere", None), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd-other-dir", + None, + ), + ]; + + for &(root, mount_point, subsys, expected) in cases.iter() { + let mnt_info = MountInfo { + version: CgroupVersion::V1, + root: root.into(), + mount_point: mount_point.into(), + }; + let subsys = Subsys { + version: CgroupVersion::V1, + base: subsys.into(), + }; + + let actual = Cgroup::translate(mnt_info, subsys).map(|c| c.base); + let expected = expected.map(PathBuf::from); + assert_eq!(actual, expected); + } + } + + #[test] + fn test_cgroup_cpu_quota() { + let cgroup = Cgroup::new(CgroupVersion::V1, join!(FIXTURES_CGROUPS, "good")); + assert_eq!(cgroup.cpu_quota(), Some(6)); + } + + #[test] + fn test_cgroup_cpu_quota_divide_by_zero() { + let cgroup = Cgroup::new(CgroupVersion::V1, join!(FIXTURES_CGROUPS, "zero-period")); + assert!(cgroup.quota_us().is_some()); + assert_eq!(cgroup.period_us(), Some(0)); + assert_eq!(cgroup.cpu_quota(), None); + } + + #[test] + fn test_cgroup_cpu_quota_ceil() { + let cgroup = Cgroup::new(CgroupVersion::V1, join!(FIXTURES_CGROUPS, "ceil")); + assert_eq!(cgroup.cpu_quota(), Some(2)); + } + } + + mod v2 { + use super::super::{Cgroup, CgroupVersion, MountInfo, Subsys}; + use std::path::{Path, PathBuf}; + + // `static_in_const` feature is not stable in Rust 1.13. + static FIXTURES_PROC: &'static str = "fixtures/cgroups2/proc/cgroups"; + + static FIXTURES_CGROUPS: &'static str = "fixtures/cgroups2/cgroups"; + + macro_rules! join { + ($base:expr, $($path:expr),+) => ({ + Path::new($base) + $(.join($path))+ + }) + } + + #[test] + fn test_load_mountinfo() { + // test only one optional fields + let path = join!(FIXTURES_PROC, "mountinfo"); + + let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V2).unwrap(); + + assert_eq!(mnt_info.root, "/"); + assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup"); + } + + #[test] + fn test_load_subsys() { + let path = join!(FIXTURES_PROC, "cgroup"); + + let subsys = Subsys::load_cpu(path).unwrap(); + + assert_eq!(subsys.base, "/"); + assert_eq!(subsys.version, CgroupVersion::V2); + } + + #[test] + fn test_load_subsys_multi() { + let path = join!(FIXTURES_PROC, "cgroup_multi"); + + let subsys = Subsys::load_cpu(path).unwrap(); + + assert_eq!(subsys.base, "/"); + assert_eq!(subsys.version, CgroupVersion::V1); + } + + #[test] + fn test_cgroup_mount() { + let cases = &[ + ("/", "/sys/fs/cgroup/cpu", "/", Some("/sys/fs/cgroup/cpu")), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd", + Some("/sys/fs/cgroup/cpu"), + ), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd/", + Some("/sys/fs/cgroup/cpu"), + ), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd/large", + Some("/sys/fs/cgroup/cpu/large"), + ), + // fails + ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/", None), + ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/docker", None), + ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/elsewhere", None), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd-other-dir", + None, + ), + ]; + + for &(root, mount_point, subsys, expected) in cases.iter() { + let mnt_info = MountInfo { + version: CgroupVersion::V1, + root: root.into(), + mount_point: mount_point.into(), + }; + let subsys = Subsys { + version: CgroupVersion::V1, + base: subsys.into(), + }; + + let actual = Cgroup::translate(mnt_info, subsys).map(|c| c.base); + let expected = expected.map(PathBuf::from); + assert_eq!(actual, expected); + } + } + + #[test] + fn test_cgroup_cpu_quota() { + let cgroup = Cgroup::new(CgroupVersion::V2, join!(FIXTURES_CGROUPS, "good")); + assert_eq!(cgroup.cpu_quota(), Some(6)); + } + + #[test] + fn test_cgroup_cpu_quota_divide_by_zero() { + let cgroup = Cgroup::new(CgroupVersion::V2, join!(FIXTURES_CGROUPS, "zero-period")); + let period = cgroup.max().map(|max| max.1); + + assert_eq!(period, Some(0)); + assert_eq!(cgroup.cpu_quota(), None); + } + + #[test] + fn test_cgroup_cpu_quota_ceil() { + let cgroup = Cgroup::new(CgroupVersion::V2, join!(FIXTURES_CGROUPS, "ceil")); + assert_eq!(cgroup.cpu_quota(), Some(2)); + } + } +} |