1 use std
::collections
::HashMap
;
3 use std
::io
::{BufRead, BufReader, Read}
;
5 use std
::path
::{Path, PathBuf}
;
6 use std
::sync
::atomic
::{AtomicUsize, Ordering}
;
12 ($
($args
:expr
),*) => ({
25 debug
!("NONE: {:?}", stringify
!($e
));
32 pub fn get_num_cpus() -> usize {
33 match cgroups_num_cpus() {
35 None
=> logical_cpus(),
39 fn logical_cpus() -> usize {
40 let mut set
: libc
::cpu_set_t
= unsafe { mem::zeroed() }
;
41 if unsafe { libc::sched_getaffinity(0, mem::size_of::<libc::cpu_set_t>(), &mut set) }
== 0 {
42 let mut count
: u32 = 0;
43 for i
in 0..libc
::CPU_SETSIZE
as usize {
44 if unsafe { libc::CPU_ISSET(i, &set) }
{
50 let cpus
= unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) }
;
59 pub fn get_num_physical_cpus() -> usize {
60 let file
= match File
::open("/proc/cpuinfo") {
62 Err(_
) => return get_num_cpus(),
64 let reader
= BufReader
::new(file
);
65 let mut map
= HashMap
::new();
66 let mut physid
: u32 = 0;
67 let mut cores
: usize = 0;
69 for line
in reader
.lines().filter_map(|result
| result
.ok()) {
70 let mut it
= line
.split('
:'
);
71 let (key
, value
) = match (it
.next(), it
.next()) {
72 (Some(key
), Some(value
)) => (key
.trim(), value
.trim()),
75 if key
== "physical id" {
77 Ok(val
) => physid
= val
,
82 if key
== "cpu cores" {
84 Ok(val
) => cores
= val
,
90 map
.insert(physid
, cores
);
94 let count
= map
.into_iter().fold(0, |acc
, (_
, cores
)| acc
+ cores
);
103 /// Cached CPUs calculated from cgroups.
105 /// If 0, check logical cpus.
106 // Allow deprecation warnings, we want to work on older rustc
108 static CGROUPS_CPUS
: AtomicUsize
= ::std
::sync
::atomic
::ATOMIC_USIZE_INIT
;
110 fn cgroups_num_cpus() -> Option
<usize> {
112 static ONCE
: Once
= ::std
::sync
::ONCE_INIT
;
114 ONCE
.call_once(init_cgroups
);
116 let cpus
= CGROUPS_CPUS
.load(Ordering
::Acquire
);
126 // Should only be called once
127 debug_assert
!(CGROUPS_CPUS
.load(Ordering
::SeqCst
) == 0);
129 if let Some(quota
) = load_cgroups("/proc/self/cgroup", "/proc/self/mountinfo") {
134 let logical
= logical_cpus();
135 let count
= ::std
::cmp
::min(quota
, logical
);
137 CGROUPS_CPUS
.store(count
, Ordering
::SeqCst
);
141 fn load_cgroups
<P1
, P2
>(cgroup_proc
: P1
, mountinfo_proc
: P2
) -> Option
<usize>
146 let subsys
= some
!(Subsys
::load_cpu(cgroup_proc
));
147 let mntinfo
= some
!(MountInfo
::load_cpu(mountinfo_proc
));
148 let cgroup
= some
!(Cgroup
::translate(mntinfo
, subsys
));
166 fn new(dir
: PathBuf
) -> Cgroup
{
170 fn translate(mntinfo
: MountInfo
, subsys
: Subsys
) -> Option
<Cgroup
> {
171 // Translate the subsystem directory via the host paths.
173 "subsys = {:?}; root = {:?}; mount_point = {:?}",
174 subsys
.base
, mntinfo
.root
, mntinfo
.mount_point
177 let rel_from_root
= some
!(Path
::new(&subsys
.base
).strip_prefix(&mntinfo
.root
).ok());
179 debug
!("rel_from_root: {:?}", rel_from_root
);
181 // join(mp.MountPoint, relPath)
182 let mut path
= PathBuf
::from(mntinfo
.mount_point
);
183 path
.push(rel_from_root
);
184 Some(Cgroup
::new(path
))
187 fn cpu_quota(&self) -> Option
<usize> {
188 let quota_us
= some
!(self.quota_us());
189 let period_us
= some
!(self.period_us());
191 // protect against dividing by zero
196 // Ceil the division, since we want to be able to saturate
197 // the available CPUs, and flooring would leave a CPU un-utilized.
199 Some((quota_us
as f64 / period_us
as f64).ceil() as usize)
202 fn quota_us(&self) -> Option
<usize> {
203 self.param("cpu.cfs_quota_us")
206 fn period_us(&self) -> Option
<usize> {
207 self.param("cpu.cfs_period_us")
210 fn param(&self, param
: &str) -> Option
<usize> {
211 let mut file
= some
!(File
::open(self.base
.join(param
)).ok());
213 let mut buf
= String
::new();
214 some
!(file
.read_to_string(&mut buf
).ok());
216 buf
.trim().parse().ok()
221 fn load_cpu
<P
: AsRef
<Path
>>(proc_path
: P
) -> Option
<MountInfo
> {
222 let file
= some
!(File
::open(proc_path
).ok());
223 let file
= BufReader
::new(file
);
226 .filter_map(|result
| result
.ok())
227 .filter_map(MountInfo
::parse_line
)
231 fn parse_line(line
: String
) -> Option
<MountInfo
> {
232 let mut fields
= line
.split(' '
);
234 // 7 5 0:6 </> /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct
235 let mnt_root
= some
!(fields
.nth(3));
236 // 7 5 0:6 / </sys/fs/cgroup/cpu,cpuacct> rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct
237 let mnt_point
= some
!(fields
.next());
239 // Ignore all fields until the separator(-).
240 // Note: there could be zero or more optional fields before hyphen.
241 // See: https://man7.org/linux/man-pages/man5/proc.5.html
242 // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 <-> cgroup cgroup rw,cpu,cpuacct
243 // Note: we cannot use `?` here because we need to support Rust 1.13.
244 match fields
.find(|&s
| s
== "-") {
249 // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - <cgroup> cgroup rw,cpu,cpuacct
250 if fields
.next() != Some("cgroup") {
254 // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup <rw,cpu,cpuacct>
255 let super_opts
= some
!(fields
.nth(1));
257 // We only care about the 'cpu' option
258 if !super_opts
.split('
,'
).any(|opt
| opt
== "cpu") {
263 root
: mnt_root
.to_owned(),
264 mount_point
: mnt_point
.to_owned(),
270 fn load_cpu
<P
: AsRef
<Path
>>(proc_path
: P
) -> Option
<Subsys
> {
271 let file
= some
!(File
::open(proc_path
).ok());
272 let file
= BufReader
::new(file
);
275 .filter_map(|result
| result
.ok())
276 .filter_map(Subsys
::parse_line
)
280 fn parse_line(line
: String
) -> Option
<Subsys
> {
283 let mut fields
= line
.split('
:'
);
285 let sub_systems
= some
!(fields
.nth(1));
287 if !sub_systems
.split('
,'
).any(|sub
| sub
== "cpu") {
291 fields
.next().map(|path
| Subsys
{
292 base
: path
.to_owned(),
299 use super::{Cgroup, MountInfo, Subsys}
;
300 use std
::path
::{Path, PathBuf}
;
302 // `static_in_const` feature is not stable in Rust 1.13.
303 static FIXTURES_PROC
: &'
static str = "fixtures/cgroups/proc/cgroups";
305 static FIXTURES_CGROUPS
: &'
static str = "fixtures/cgroups/cgroups";
308 ($base
:expr
, $
($path
:expr
),+) => ({
315 fn test_load_mountinfo() {
316 // test only one optional fields
317 let path
= join
!(FIXTURES_PROC
, "mountinfo");
319 let mnt_info
= MountInfo
::load_cpu(path
).unwrap();
321 assert_eq
!(mnt_info
.root
, "/");
322 assert_eq
!(mnt_info
.mount_point
, "/sys/fs/cgroup/cpu,cpuacct");
324 // test zero optional field
325 let path
= join
!(FIXTURES_PROC
, "mountinfo_zero_opt");
327 let mnt_info
= MountInfo
::load_cpu(path
).unwrap();
329 assert_eq
!(mnt_info
.root
, "/");
330 assert_eq
!(mnt_info
.mount_point
, "/sys/fs/cgroup/cpu,cpuacct");
332 // test multi optional fields
333 let path
= join
!(FIXTURES_PROC
, "mountinfo_multi_opt");
335 let mnt_info
= MountInfo
::load_cpu(path
).unwrap();
337 assert_eq
!(mnt_info
.root
, "/");
338 assert_eq
!(mnt_info
.mount_point
, "/sys/fs/cgroup/cpu,cpuacct");
342 fn test_load_subsys() {
343 let path
= join
!(FIXTURES_PROC
, "cgroup");
345 let subsys
= Subsys
::load_cpu(path
).unwrap();
347 assert_eq
!(subsys
.base
, "/");
351 fn test_cgroup_mount() {
353 ("/", "/sys/fs/cgroup/cpu", "/", Some("/sys/fs/cgroup/cpu")),
356 "/sys/fs/cgroup/cpu",
358 Some("/sys/fs/cgroup/cpu"),
362 "/sys/fs/cgroup/cpu",
364 Some("/sys/fs/cgroup/cpu"),
368 "/sys/fs/cgroup/cpu",
369 "/docker/01abcd/large",
370 Some("/sys/fs/cgroup/cpu/large"),
373 ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/", None
),
374 ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/docker", None
),
375 ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/elsewhere", None
),
378 "/sys/fs/cgroup/cpu",
379 "/docker/01abcd-other-dir",
384 for &(root
, mount_point
, subsys
, expected
) in cases
.iter() {
385 let mnt_info
= MountInfo
{
387 mount_point
: mount_point
.into(),
389 let subsys
= Subsys
{
393 let actual
= Cgroup
::translate(mnt_info
, subsys
).map(|c
| c
.base
);
394 let expected
= expected
.map(PathBuf
::from
);
395 assert_eq
!(actual
, expected
);
400 fn test_cgroup_cpu_quota() {
401 let cgroup
= Cgroup
::new(join
!(FIXTURES_CGROUPS
, "good"));
402 assert_eq
!(cgroup
.cpu_quota(), Some(6));
406 fn test_cgroup_cpu_quota_divide_by_zero() {
407 let cgroup
= Cgroup
::new(join
!(FIXTURES_CGROUPS
, "zero-period"));
408 assert
!(cgroup
.quota_us().is_some());
409 assert_eq
!(cgroup
.period_us(), Some(0));
410 assert_eq
!(cgroup
.cpu_quota(), None
);
414 fn test_cgroup_cpu_quota_ceil() {
415 let cgroup
= Cgroup
::new(join
!(FIXTURES_CGROUPS
, "ceil"));
416 assert_eq
!(cgroup
.cpu_quota(), Some(2));