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