1 use std::collections::HashMap;
2 use std::fs::File;
3 use std::io::{BufRead, BufReader, Read};
4 use std::mem;
5 use std::path::{Path, PathBuf};
6 use std::sync::atomic::{AtomicUsize, Ordering};
7 use std::sync::Once;
8 
9 use libc;
10 
11 macro_rules! debug {
12     ($($args:expr),*) => ({
13         if false {
14         //if true {
15             println!($($args),*);
16         }
17     });
18 }
19 
20 macro_rules! some {
21     ($e:expr) => ({
22         match $e {
23             Some(v) => v,
24             None => {
25                 debug!("NONE: {:?}", stringify!($e));
26                 return None;
27             }
28         }
29     })
30 }
31 
get_num_cpus() -> usize32 pub fn get_num_cpus() -> usize {
33     match cgroups_num_cpus() {
34         Some(n) => n,
35         None => logical_cpus(),
36     }
37 }
38 
logical_cpus() -> usize39 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) } {
45                 count += 1
46             }
47         }
48         count as usize
49     } else {
50         let cpus = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) };
51         if cpus < 1 {
52             1
53         } else {
54             cpus as usize
55         }
56     }
57 }
58 
get_num_physical_cpus() -> usize59 pub fn get_num_physical_cpus() -> usize {
60     let file = match File::open("/proc/cpuinfo") {
61         Ok(val) => val,
62         Err(_) => return get_num_cpus(),
63     };
64     let reader = BufReader::new(file);
65     let mut map = HashMap::new();
66     let mut physid: u32 = 0;
67     let mut cores: usize = 0;
68     let mut chgcount = 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()),
73             _ => continue,
74         };
75         if key == "physical id" {
76             match value.parse() {
77                 Ok(val) => physid = val,
78                 Err(_) => break,
79             };
80             chgcount += 1;
81         }
82         if key == "cpu cores" {
83             match value.parse() {
84                 Ok(val) => cores = val,
85                 Err(_) => break,
86             };
87             chgcount += 1;
88         }
89         if chgcount == 2 {
90             map.insert(physid, cores);
91             chgcount = 0;
92         }
93     }
94     let count = map.into_iter().fold(0, |acc, (_, cores)| acc + cores);
95 
96     if count == 0 {
97         get_num_cpus()
98     } else {
99         count
100     }
101 }
102 
103 /// Cached CPUs calculated from cgroups.
104 ///
105 /// If 0, check logical cpus.
106 // Allow deprecation warnings, we want to work on older rustc
107 #[allow(warnings)]
108 static CGROUPS_CPUS: AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT;
109 
cgroups_num_cpus() -> Option<usize>110 fn cgroups_num_cpus() -> Option<usize> {
111     #[allow(warnings)]
112     static ONCE: Once = ::std::sync::ONCE_INIT;
113 
114     ONCE.call_once(init_cgroups);
115 
116     let cpus = CGROUPS_CPUS.load(Ordering::Acquire);
117 
118     if cpus > 0 {
119         Some(cpus)
120     } else {
121         None
122     }
123 }
124 
init_cgroups()125 fn init_cgroups() {
126     // Should only be called once
127     debug_assert!(CGROUPS_CPUS.load(Ordering::SeqCst) == 0);
128 
129     match load_cgroups("/proc/self/cgroup", "/proc/self/mountinfo") {
130         Some(quota) => {
131             if quota == 0 {
132                 return;
133             }
134 
135             let logical = logical_cpus();
136             let count = ::std::cmp::min(quota, logical);
137 
138             CGROUPS_CPUS.store(count, Ordering::SeqCst);
139         }
140         None => return,
141     }
142 }
143 
load_cgroups<P1, P2>(cgroup_proc: P1, mountinfo_proc: P2) -> Option<usize> where P1: AsRef<Path>, P2: AsRef<Path>,144 fn load_cgroups<P1, P2>(cgroup_proc: P1, mountinfo_proc: P2) -> Option<usize>
145 where
146     P1: AsRef<Path>,
147     P2: AsRef<Path>,
148 {
149     let subsys = some!(Subsys::load_cpu(cgroup_proc));
150     let mntinfo = some!(MountInfo::load_cpu(mountinfo_proc));
151     let cgroup = some!(Cgroup::translate(mntinfo, subsys));
152     cgroup.cpu_quota()
153 }
154 
155 struct Cgroup {
156     base: PathBuf,
157 }
158 
159 struct MountInfo {
160     root: String,
161     mount_point: String,
162 }
163 
164 struct Subsys {
165     base: String,
166 }
167 
168 impl Cgroup {
new(dir: PathBuf) -> Cgroup169     fn new(dir: PathBuf) -> Cgroup {
170         Cgroup {
171             base: dir,
172         }
173     }
174 
translate(mntinfo: MountInfo, subsys: Subsys) -> Option<Cgroup>175     fn translate(mntinfo: MountInfo, subsys: Subsys) -> Option<Cgroup> {
176         // Translate the subsystem directory via the host paths.
177         debug!(
178             "subsys = {:?}; root = {:?}; mount_point = {:?}",
179             subsys.base,
180             mntinfo.root,
181             mntinfo.mount_point
182         );
183 
184         let rel_from_root = some!(Path::new(&subsys.base).strip_prefix(&mntinfo.root).ok());
185 
186         debug!("rel_from_root: {:?}", rel_from_root);
187 
188         // join(mp.MountPoint, relPath)
189         let mut path = PathBuf::from(mntinfo.mount_point);
190         path.push(rel_from_root);
191         Some(Cgroup::new(path))
192     }
193 
cpu_quota(&self) -> Option<usize>194     fn cpu_quota(&self) -> Option<usize> {
195         let quota_us = some!(self.quota_us());
196         let period_us = some!(self.period_us());
197 
198         // protect against dividing by zero
199         if period_us == 0 {
200             return None;
201         }
202 
203         // Ceil the division, since we want to be able to saturate
204         // the available CPUs, and flooring would leave a CPU un-utilized.
205 
206         Some((quota_us as f64 / period_us as f64).ceil() as usize)
207     }
208 
quota_us(&self) -> Option<usize>209     fn quota_us(&self) -> Option<usize> {
210         self.param("cpu.cfs_quota_us")
211     }
212 
period_us(&self) -> Option<usize>213     fn period_us(&self) -> Option<usize> {
214         self.param("cpu.cfs_period_us")
215     }
216 
param(&self, param: &str) -> Option<usize>217     fn param(&self, param: &str) -> Option<usize> {
218         let mut file = some!(File::open(self.base.join(param)).ok());
219 
220         let mut buf = String::new();
221         some!(file.read_to_string(&mut buf).ok());
222 
223         buf.trim().parse().ok()
224     }
225 }
226 
227 impl MountInfo {
load_cpu<P: AsRef<Path>>(proc_path: P) -> Option<MountInfo>228     fn load_cpu<P: AsRef<Path>>(proc_path: P) -> Option<MountInfo> {
229         let file = some!(File::open(proc_path).ok());
230         let file = BufReader::new(file);
231 
232         file.lines()
233             .filter_map(|result| result.ok())
234             .filter_map(MountInfo::parse_line)
235             .next()
236     }
237 
parse_line(line: String) -> Option<MountInfo>238     fn parse_line(line: String) -> Option<MountInfo> {
239         let mut fields = line.split(' ');
240 
241         let mnt_root = some!(fields.nth(3));
242         let mnt_point = some!(fields.nth(0));
243 
244         if fields.nth(3) != Some("cgroup") {
245             return None;
246         }
247 
248         let super_opts = some!(fields.nth(1));
249 
250         // We only care about the 'cpu' option
251         if !super_opts.split(',').any(|opt| opt == "cpu") {
252             return None;
253         }
254 
255         Some(MountInfo {
256             root: mnt_root.to_owned(),
257             mount_point: mnt_point.to_owned(),
258         })
259     }
260 }
261 
262 impl Subsys {
load_cpu<P: AsRef<Path>>(proc_path: P) -> Option<Subsys>263     fn load_cpu<P: AsRef<Path>>(proc_path: P) -> Option<Subsys> {
264         let file = some!(File::open(proc_path).ok());
265         let file = BufReader::new(file);
266 
267         file.lines()
268             .filter_map(|result| result.ok())
269             .filter_map(Subsys::parse_line)
270             .next()
271     }
272 
parse_line(line: String) -> Option<Subsys>273     fn parse_line(line: String) -> Option<Subsys> {
274         // Example format:
275         // 11:cpu,cpuacct:/
276         let mut fields = line.split(':');
277 
278         let sub_systems = some!(fields.nth(1));
279 
280         if !sub_systems.split(',').any(|sub| sub == "cpu") {
281             return None;
282         }
283 
284         fields.next().map(|path| Subsys { base: path.to_owned() })
285     }
286 }
287 
288 #[cfg(test)]
289 mod tests {
290     use std::path::{Path, PathBuf};
291     use super::{Cgroup, MountInfo, Subsys};
292 
293 
294     static FIXTURES_PROC: &'static str = "fixtures/cgroups/proc/cgroups";
295 
296     static FIXTURES_CGROUPS: &'static str = "fixtures/cgroups/cgroups";
297 
298     macro_rules! join {
299         ($base:expr, $($path:expr),+) => ({
300             Path::new($base)
301                 $(.join($path))+
302         })
303     }
304 
305     #[test]
test_load_mountinfo()306     fn test_load_mountinfo() {
307         let path = join!(FIXTURES_PROC, "mountinfo");
308 
309         let mnt_info = MountInfo::load_cpu(path).unwrap();
310 
311         assert_eq!(mnt_info.root, "/");
312         assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct");
313     }
314 
315     #[test]
test_load_subsys()316     fn test_load_subsys() {
317         let path = join!(FIXTURES_PROC, "cgroup");
318 
319         let subsys = Subsys::load_cpu(path).unwrap();
320 
321         assert_eq!(subsys.base, "/");
322     }
323 
324     #[test]
test_cgroup_mount()325     fn test_cgroup_mount() {
326         let cases = &[
327             (
328                 "/",
329                 "/sys/fs/cgroup/cpu",
330                 "/",
331                 Some("/sys/fs/cgroup/cpu"),
332             ),
333             (
334                 "/docker/01abcd",
335                 "/sys/fs/cgroup/cpu",
336                 "/docker/01abcd",
337                 Some("/sys/fs/cgroup/cpu"),
338             ),
339             (
340                 "/docker/01abcd",
341                 "/sys/fs/cgroup/cpu",
342                 "/docker/01abcd/",
343                 Some("/sys/fs/cgroup/cpu"),
344             ),
345             (
346                 "/docker/01abcd",
347                 "/sys/fs/cgroup/cpu",
348                 "/docker/01abcd/large",
349                 Some("/sys/fs/cgroup/cpu/large"),
350             ),
351 
352             // fails
353 
354             (
355                 "/docker/01abcd",
356                 "/sys/fs/cgroup/cpu",
357                 "/",
358                 None,
359             ),
360             (
361                 "/docker/01abcd",
362                 "/sys/fs/cgroup/cpu",
363                 "/docker",
364                 None,
365             ),
366             (
367                 "/docker/01abcd",
368                 "/sys/fs/cgroup/cpu",
369                 "/elsewhere",
370                 None,
371             ),
372             (
373                 "/docker/01abcd",
374                 "/sys/fs/cgroup/cpu",
375                 "/docker/01abcd-other-dir",
376                 None,
377             ),
378         ];
379 
380         for &(root, mount_point, subsys, expected) in cases.iter() {
381             let mnt_info = MountInfo {
382                 root: root.into(),
383                 mount_point: mount_point.into(),
384             };
385             let subsys = Subsys {
386                 base: subsys.into(),
387             };
388 
389             let actual = Cgroup::translate(mnt_info, subsys).map(|c| c.base);
390             let expected = expected.map(|s| PathBuf::from(s));
391             assert_eq!(actual, expected);
392         }
393     }
394 
395     #[test]
test_cgroup_cpu_quota()396     fn test_cgroup_cpu_quota() {
397         let cgroup = Cgroup::new(join!(FIXTURES_CGROUPS, "good"));
398         assert_eq!(cgroup.cpu_quota(), Some(6));
399     }
400 
401     #[test]
test_cgroup_cpu_quota_divide_by_zero()402     fn test_cgroup_cpu_quota_divide_by_zero() {
403         let cgroup = Cgroup::new(join!(FIXTURES_CGROUPS, "zero-period"));
404         assert!(cgroup.quota_us().is_some());
405         assert_eq!(cgroup.period_us(), Some(0));
406         assert_eq!(cgroup.cpu_quota(), None);
407     }
408 
409     #[test]
test_cgroup_cpu_quota_ceil()410     fn test_cgroup_cpu_quota_ceil() {
411         let cgroup = Cgroup::new(join!(FIXTURES_CGROUPS, "ceil"));
412         assert_eq!(cgroup.cpu_quota(), Some(2));
413     }
414 }
415