Skip to content

Commit f8d653e

Browse files
committed
cpu: change cfs_quota from u64 to i64
cfs_quota can be set to -1 to indicate no limit Fixes: levex#5 Signed-off-by: bin liu <[email protected]>
1 parent d20e6a5 commit f8d653e

File tree

3 files changed

+166
-20
lines changed

3 files changed

+166
-20
lines changed

src/cpu.rs

Lines changed: 93 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ use std::path::PathBuf;
1515

1616
use crate::error::ErrorKind::*;
1717
use crate::error::*;
18+
use crate::{parse_max_value, read_i64_from};
1819

1920
use crate::{
20-
ControllIdentifier, ControllerInternal, Controllers, CpuResources, Resources, Subsystem,
21+
ControllIdentifier, ControllerInternal, Controllers, CpuResources, MaxValue, Resources,
22+
Subsystem,
2123
};
2224

2325
/// A controller that allows controlling the `cpu` subsystem of a Cgroup.
@@ -41,6 +43,13 @@ pub struct Cpu {
4143
pub stat: String,
4244
}
4345

46+
/// The current state of the control group and its processes.
47+
#[derive(Debug)]
48+
struct CFSQuotaAndPeriod {
49+
quota: MaxValue,
50+
period: u64,
51+
}
52+
4453
impl ControllerInternal for CpuController {
4554
fn control_type(&self) -> Controllers {
4655
Controllers::Cpu
@@ -77,8 +86,8 @@ impl ControllerInternal for CpuController {
7786
return Err(Error::new(ErrorKind::Other));
7887
}
7988

80-
let _ = self.set_cfs_quota(res.quota as u64);
81-
if self.cfs_quota()? != res.quota as u64 {
89+
let _ = self.set_cfs_quota(res.quota);
90+
if self.cfs_quota()? != res.quota {
8291
return Err(Error::new(ErrorKind::Other));
8392
}
8493

@@ -182,6 +191,9 @@ impl CpuController {
182191
/// Specify a period (when using the CFS scheduler) of time in microseconds for how often this
183192
/// control group's access to the CPU should be reallocated.
184193
pub fn set_cfs_period(&self, us: u64) -> Result<()> {
194+
if self.v2 {
195+
return self.set_cfs_quota_and_period(None, Some(us));
196+
}
185197
self.open_path("cpu.cfs_period_us", true)
186198
.and_then(|mut file| {
187199
file.write_all(us.to_string().as_ref())
@@ -192,13 +204,22 @@ impl CpuController {
192204
/// Retrieve the period of time of how often this cgroup's access to the CPU should be
193205
/// reallocated in microseconds.
194206
pub fn cfs_period(&self) -> Result<u64> {
207+
if self.v2 {
208+
let current_value = self
209+
.open_path("cpu.max", false)
210+
.and_then(parse_cfs_quota_and_period)?;
211+
return Ok(current_value.period);
212+
}
195213
self.open_path("cpu.cfs_period_us", false)
196214
.and_then(read_u64_from)
197215
}
198216

199217
/// Specify a quota (when using the CFS scheduler) of time in microseconds for which all tasks
200218
/// in this control group can run during one period (see: `set_cfs_period()`).
201-
pub fn set_cfs_quota(&self, us: u64) -> Result<()> {
219+
pub fn set_cfs_quota(&self, us: i64) -> Result<()> {
220+
if self.v2 {
221+
return self.set_cfs_quota_and_period(Some(us), None);
222+
}
202223
self.open_path("cpu.cfs_quota_us", true)
203224
.and_then(|mut file| {
204225
file.write_all(us.to_string().as_ref())
@@ -208,28 +229,59 @@ impl CpuController {
208229

209230
/// Retrieve the quota of time for which all tasks in this cgroup can run during one period, in
210231
/// microseconds.
211-
pub fn cfs_quota(&self) -> Result<u64> {
232+
pub fn cfs_quota(&self) -> Result<i64> {
233+
if self.v2 {
234+
let current_value = self
235+
.open_path("cpu.max", false)
236+
.and_then(parse_cfs_quota_and_period)?;
237+
return Ok(current_value.quota.to_i64());
238+
}
239+
212240
self.open_path("cpu.cfs_quota_us", false)
213-
.and_then(read_u64_from)
241+
.and_then(read_i64_from)
214242
}
215243

216-
pub fn set_cfs_quota_and_period(&self, quota: u64, period: u64) -> Result<()> {
244+
pub fn set_cfs_quota_and_period(&self, quota: Option<i64>, period: Option<u64>) -> Result<()> {
217245
if !self.v2 {
218-
self.set_cfs_quota(quota)?;
219-
return self.set_cfs_period(period);
220-
}
221-
let mut line = "max".to_string();
222-
if quota > 0 {
223-
line = quota.to_string();
246+
if let Some(q) = quota {
247+
self.set_cfs_quota(q)?;
248+
}
249+
if let Some(p) = period {
250+
self.set_cfs_period(p)?;
251+
}
252+
return Ok(());
224253
}
225254

226-
let mut p = period;
227-
if period == 0 {
228-
// This default value is documented in
229-
// https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
230-
p = 100000
231-
}
232-
line = format!("{} {}", line, p);
255+
// https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
256+
257+
// cpu.max
258+
// A read-write two value file which exists on non-root cgroups. The default is “max 100000”.
259+
// The maximum bandwidth limit. It’s in the following format:
260+
// $MAX $PERIOD
261+
// which indicates that the group may consume upto $MAX in each $PERIOD duration.
262+
// “max” for $MAX indicates no limit. If only one number is written, $MAX is updated.
263+
264+
let current_value = self
265+
.open_path("cpu.max", false)
266+
.and_then(parse_cfs_quota_and_period)?;
267+
268+
let new_quota = if let Some(q) = quota {
269+
if q > 0 {
270+
q.to_string()
271+
} else {
272+
"max".to_string()
273+
}
274+
} else {
275+
current_value.quota.to_string()
276+
};
277+
278+
let new_period = if let Some(p) = period {
279+
p.to_string()
280+
} else {
281+
current_value.period.to_string()
282+
};
283+
284+
let line = format!("{} {}", new_quota, new_period);
233285
self.open_path("cpu.max", true).and_then(|mut file| {
234286
file.write_all(line.as_ref())
235287
.map_err(|e| Error::with_cause(WriteFailed, e))
@@ -252,3 +304,24 @@ impl CpuController {
252304
})
253305
}
254306
}
307+
308+
fn parse_cfs_quota_and_period(mut file: File) -> Result<CFSQuotaAndPeriod> {
309+
let mut content = String::new();
310+
file.read_to_string(&mut content)
311+
.map_err(|e| Error::with_cause(ReadFailed, e))?;
312+
313+
let fields = content.trim().split(' ').collect::<Vec<&str>>();
314+
if fields.len() != 2 {
315+
return Err(Error::from_string(format!("invaild format: {}", content)));
316+
}
317+
318+
let quota = parse_max_value(&fields[0].to_string())?;
319+
let period = fields[1]
320+
.parse::<u64>()
321+
.map_err(|e| Error::with_cause(ParseError, e))?;
322+
323+
Ok(CFSQuotaAndPeriod {
324+
quota: quota,
325+
period: period,
326+
})
327+
}

src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,3 +768,15 @@ pub fn libc_rmdir(p: &str) {
768768
// with int return value
769769
let _ = unsafe { libc::rmdir(p.as_ptr() as *const i8) };
770770
}
771+
772+
/// read and parse an i64 data
773+
pub fn read_i64_from(mut file: File) -> Result<i64> {
774+
let mut string = String::new();
775+
match file.read_to_string(&mut string) {
776+
Ok(_) => string
777+
.trim()
778+
.parse()
779+
.map_err(|e| Error::with_cause(ParseError, e)),
780+
Err(e) => Err(Error::with_cause(ReadFailed, e)),
781+
}
782+
}

tests/cpu.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2020 And Group
2+
//
3+
// SPDX-License-Identifier: Apache-2.0 or MIT
4+
//
5+
6+
//! Simple unit tests about the CPU control groups system.
7+
use cgroups::cpu::CpuController;
8+
use cgroups::error::ErrorKind;
9+
use cgroups::{Cgroup, CgroupPid, CpuResources, Hierarchy, Resources};
10+
11+
use std::fs;
12+
13+
#[test]
14+
fn test_cfs_quota_and_periods() {
15+
let h = cgroups::hierarchies::auto();
16+
let h = Box::new(&*h);
17+
let cg = Cgroup::new(h, String::from("test_cfs_quota_and_periods"));
18+
19+
let cpu_controller: &CpuController = cg.controller_of().unwrap();
20+
21+
let current_quota = cpu_controller.cfs_quota().unwrap();
22+
let current_peroid = cpu_controller.cfs_period().unwrap();
23+
24+
// verify default value
25+
// The default is “max 100000”.
26+
assert_eq!(-1, current_quota);
27+
assert_eq!(100000, current_peroid);
28+
29+
// case 1 set quota
30+
let r = cpu_controller.set_cfs_quota(2000);
31+
32+
let current_quota = cpu_controller.cfs_quota().unwrap();
33+
let current_peroid = cpu_controller.cfs_period().unwrap();
34+
assert_eq!(2000, current_quota);
35+
assert_eq!(100000, current_peroid);
36+
37+
// case 2 set period
38+
cpu_controller.set_cfs_period(1000000);
39+
let current_quota = cpu_controller.cfs_quota().unwrap();
40+
let current_peroid = cpu_controller.cfs_period().unwrap();
41+
assert_eq!(2000, current_quota);
42+
assert_eq!(1000000, current_peroid);
43+
44+
// case 3 set both quota and period
45+
cpu_controller.set_cfs_quota_and_period(Some(5000), Some(100000));
46+
47+
let current_quota = cpu_controller.cfs_quota().unwrap();
48+
let current_peroid = cpu_controller.cfs_period().unwrap();
49+
assert_eq!(5000, current_quota);
50+
assert_eq!(100000, current_peroid);
51+
52+
// case 4 set both quota and period, set quota to -1
53+
cpu_controller.set_cfs_quota_and_period(Some(-1), None);
54+
55+
let current_quota = cpu_controller.cfs_quota().unwrap();
56+
let current_peroid = cpu_controller.cfs_period().unwrap();
57+
assert_eq!(-1, current_quota);
58+
assert_eq!(100000, current_peroid);
59+
60+
cg.delete();
61+
}

0 commit comments

Comments
 (0)