controlgroup/v1/
cpu.rs

1//! Operations on a CPU subsystem.
2//!
3//! [`Subsystem`] implements [`Cgroup`] trait and subsystem-specific operations.
4//!
5//! For more information about this subsystem, see the kernel's documentation
6//! [Documentation/scheduler/sched-design-CFS.txt]
7//! paragraph 7 ("GROUP SCHEDULER EXTENSIONS TO CFS"), and [Documentation/scheduler/sched-bwc.txt].
8//!
9//! # Examples
10//!
11//! ```no_run
12//! # fn main() -> controlgroup::Result<()> {
13//! use std::path::PathBuf;
14//! use controlgroup::{Pid, v1::{self, cpu, Cgroup, CgroupPath, SubsystemKind}};
15//!
16//! let mut cpu_cgroup = cpu::Subsystem::new(
17//!     CgroupPath::new(SubsystemKind::Cpu, PathBuf::from("students/charlie")));
18//! cpu_cgroup.create()?;
19//!
20//! // Define a resource limit about how a cgroup can use CPU time.
21//! let resources = cpu::Resources {
22//!     shares: Some(1024),
23//!     cfs_quota_us: Some(500_000),
24//!     cfs_period_us: Some(1_000_000),
25//!     ..cpu::Resources::default()
26//! };
27//!
28//! // Apply the resource limit to this cgroup.
29//! cpu_cgroup.apply(&resources.into())?;
30//!
31//! // Add tasks to this cgroup.
32//! let pid = Pid::from(std::process::id());
33//! cpu_cgroup.add_task(pid)?;
34//!
35//! // Do something ...
36//!
37//! // Get the throttling statistics of this cgroup.
38//! println!("{:?}", cpu_cgroup.stat()?);
39//!
40//! cpu_cgroup.remove_task(pid)?;
41//! cpu_cgroup.delete()?;
42//! # Ok(())
43//! # }
44//! ```
45//!
46//! [`Subsystem`]: struct.Subsystem.html
47//! [`Cgroup`]: ../trait.Cgroup.html
48//!
49//! [Documentation/scheduler/sched-design-CFS.txt]: https://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt
50//! [Documentation/scheduler/sched-bwc.txt]: https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt
51
52use std::path::PathBuf;
53
54use crate::{
55    parse::{parse, parse_next},
56    v1::{self, cgroup::CgroupHelper, Cgroup, CgroupPath},
57    Result,
58};
59
60/// Handler of a CPU subsystem.
61#[derive(Debug)]
62pub struct Subsystem {
63    path: CgroupPath,
64}
65
66/// Resource limit on how much CPU time a cgroup can use.
67///
68/// See the kernel's documentation for more information about the fields.
69#[derive(Debug, Default, Clone, PartialEq, Eq)]
70pub struct Resources {
71    /// Weight of how much of the total CPU time should be provided to this cgroup.
72    pub shares: Option<u64>,
73    /// Total available CPU time for this cgroup within a period (in microseconds).
74    ///
75    /// Setting -1 removes the current limit.
76    pub cfs_quota_us: Option<i64>,
77    /// Length of a period (in microseconds).
78    pub cfs_period_us: Option<u64>,
79
80    /// Total available CPU time for realtime tasks in this cgroup within a period (in microseconds).
81    ///
82    /// Setting -1 removes the current limit.
83    pub rt_runtime_us: Option<i64>,
84    /// Length of a period for realtime tasks (in microseconds).
85    pub rt_period_us: Option<u64>,
86}
87
88/// Throttling statistics of a cgroup.
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct Stat {
91    /// Number of periods (as specified in [`Resources.cfs_period_us`]) that have elapsed.
92    ///
93    /// [`Resources.cfs_period_us`]: struct.Resources.html#structfield.cfs_period_us
94    pub nr_periods: u64,
95    /// Number of times this cgroup has been throttled.
96    pub nr_throttled: u64,
97    /// Total time duration for which this cgroup has been throttled (in nanoseconds).
98    pub throttled_time: u64,
99}
100
101impl_cgroup! {
102    Subsystem, Cpu,
103
104    /// Applies the `Some` fields in `resources.cpu`.
105    fn apply(&mut self, resources: &v1::Resources) -> Result<()> {
106        let res: &self::Resources = &resources.cpu;
107
108        macro_rules! a {
109            ($field: ident, $setter: ident) => {
110                if let Some(r) = res.$field {
111                    self.$setter(r)?;
112                }
113            };
114        }
115
116        a!(shares, set_shares);
117        a!(cfs_quota_us, set_cfs_quota_us);
118        a!(cfs_period_us, set_cfs_period_us);
119        a!(rt_runtime_us, set_rt_runtime_us);
120        a!(rt_period_us, set_rt_period_us);
121
122        Ok(())
123    }
124}
125
126impl Subsystem {
127    gen_getter!(
128        cpu,
129        "the throttling statistics of this cgroup",
130        stat,
131        Stat,
132        parse_stat
133    );
134
135    gen_getter!(cpu, "the CPU time shares", shares: link, u64, parse);
136    gen_setter!(cpu, "CPU time shares", shares: link, set_shares, u64, 2048);
137
138    gen_getter!(
139        cpu,
140        "the total available CPU time within a period (in microseconds)",
141        cfs_quota_us: link,
142        i64,
143        parse
144    );
145    gen_setter!(
146        cpu,
147        "total available CPU time within a period (in microseconds)"
148            : "Setting -1 removes the current limit.",
149        cfs_quota_us : link,
150        set_cfs_quota_us,
151        quota: i64,
152        500 * 1000
153    );
154
155    gen_getter!(
156        cpu,
157        "the length of period (in microseconds)",
158        cfs_period_us: link,
159        u64,
160        parse
161    );
162    gen_setter!(
163        cpu,
164        "length of period (in microseconds)",
165        cfs_period_us: link,
166        set_cfs_period_us,
167        period: u64,
168        1000 * 1000
169    );
170
171    gen_getter!(
172        cpu,
173        "the total available CPU time for realtime tasks within a period (in microseconds)",
174        rt_runtime_us: link,
175        i64,
176        parse
177    );
178    gen_setter!(
179        cpu,
180        "total available CPU time for realtime tasks within a period (in microseconds)"
181            : "Setting -1 removes the current limit.",
182        rt_runtime_us : link,
183        set_rt_runtime_us,
184        runtime: i64,
185        500 * 1000
186    );
187
188    gen_getter!(
189        cpu,
190        "the length of period for realtime tasks (in microseconds)",
191        rt_period_us: link,
192        u64,
193        parse
194    );
195    gen_setter!(
196        cpu,
197        "the length of period for realtime tasks (in microseconds)",
198        rt_period_us: link,
199        set_rt_period_us,
200        period: u64,
201        1000 * 1000
202    );
203}
204
205fn parse_stat(reader: impl std::io::Read) -> Result<Stat> {
206    use std::io::{BufRead, BufReader};
207
208    let (mut nr_periods, mut nr_throttled, mut throttled_time) = (None, None, None);
209
210    for line in BufReader::new(reader).lines() {
211        let line = line?;
212        let mut entry = line.split_whitespace();
213
214        match entry.next() {
215            Some("nr_periods") => {
216                if nr_periods.is_some() {
217                    bail_parse!();
218                }
219                nr_periods = Some(parse_next(&mut entry)?);
220            }
221            Some("nr_throttled") => {
222                if nr_throttled.is_some() {
223                    bail_parse!();
224                }
225                nr_throttled = Some(parse_next(&mut entry)?);
226            }
227            Some("throttled_time") => {
228                if throttled_time.is_some() {
229                    bail_parse!();
230                }
231                throttled_time = Some(parse_next(&mut entry)?);
232            }
233            _ => bail_parse!(),
234        };
235
236        if entry.next().is_some() {
237            bail_parse!();
238        }
239    }
240
241    match (nr_periods, nr_throttled, throttled_time) {
242        (Some(nr_periods), Some(nr_throttled), Some(throttled_time)) => Ok(Stat {
243            nr_periods,
244            nr_throttled,
245            throttled_time,
246        }),
247        _ => {
248            bail_parse!();
249        }
250    }
251}
252
253impl Into<v1::Resources> for Resources {
254    fn into(self) -> v1::Resources {
255        v1::Resources {
256            cpu: self,
257            ..v1::Resources::default()
258        }
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265    use crate::ErrorKind;
266
267    #[test]
268    fn test_subsystem_create_file_exists() -> Result<()> {
269        gen_subsystem_test!(Cpu, ["stat", "shares", "cfs_quota_us", "cfs_period_us"])
270    }
271
272    #[test]
273    fn test_subsystem_apply() -> Result<()> {
274        gen_subsystem_test!(
275            Cpu,
276            Resources {
277                shares: Some(1024),
278                cfs_quota_us: Some(100_000),
279                cfs_period_us: Some(1_000_000),
280                rt_runtime_us: None,
281                rt_period_us: None,
282            },
283            (shares, 1024),
284            (cfs_quota_us, 100_000),
285            (cfs_period_us, 1_000_000)
286        )
287    }
288
289    #[test]
290    fn test_subsystem_stat() -> Result<()> {
291        gen_subsystem_test!(
292            Cpu,
293            stat,
294            Stat {
295                nr_periods: 0,
296                nr_throttled: 0,
297                throttled_time: 0
298            }
299        )
300    }
301
302    #[test]
303    #[ignore] // must not executed in parallel
304    fn test_subsystem_stat_throttled() -> Result<()> {
305        let mut cgroup =
306            Subsystem::new(CgroupPath::new(v1::SubsystemKind::Cpu, gen_cgroup_name!()));
307        cgroup.create()?;
308
309        let pid = crate::Pid::from(std::process::id());
310        cgroup.add_proc(pid)?;
311
312        cgroup.set_cfs_quota_us(1000)?; // 1%
313
314        crate::consume_cpu_until(|| cgroup.stat().unwrap().nr_throttled > 0, 30);
315        // dbg!(cgroup.stat()?);
316
317        let stat = cgroup.stat()?;
318        assert!(stat.nr_periods > 0);
319        assert!(stat.throttled_time > 0);
320
321        cgroup.remove_proc(pid)?;
322        cgroup.delete()
323    }
324
325    #[test]
326    fn test_subsystem_shares() -> Result<()> {
327        gen_subsystem_test!(Cpu, shares, 1024, set_shares, 2048)
328    }
329
330    #[test]
331    fn test_subsystem_cfs_quota_us() -> Result<()> {
332        gen_subsystem_test!(Cpu, cfs_quota_us, -1, set_cfs_quota_us, 100 * 1000)
333    }
334
335    #[test]
336    fn test_subsystem_cfs_period_us() -> Result<()> {
337        gen_subsystem_test!(
338            Cpu,
339            cfs_period_us,
340            100 * 1000,
341            set_cfs_period_us,
342            1000 * 1000
343        )
344    }
345
346    #[test]
347    fn test_parse_stat() -> Result<()> {
348        const CONTENT_OK: &str = "\
349nr_periods 256
350nr_throttled 8
351throttled_time 32
352";
353
354        assert_eq!(
355            parse_stat(CONTENT_OK.as_bytes())?,
356            Stat {
357                nr_periods: 256,
358                nr_throttled: 8,
359                throttled_time: 32
360            }
361        );
362
363        assert_eq!(
364            parse_stat("".as_bytes()).unwrap_err().kind(),
365            ErrorKind::Parse
366        );
367
368        const CONTENT_NG_NOT_INT: &str = "\
369nr_periods invalid
370nr_throttled 8
371throttled_time 32
372";
373
374        const CONTENT_NG_MISSING_DATA: &str = "\
375nr_periods 256
376throttled_time 32
377";
378
379        const CONTENT_NG_EXTRA_DATA: &str = "\
380nr_periods 256
381nr_throttled 8 256
382throttled_time 32
383";
384
385        const CONTENT_NG_EXTRA_ROW: &str = "\
386nr_periods 256
387nr_throttled 8
388throttled_time 32
389invalid 256
390";
391
392        for case in &[
393            CONTENT_NG_NOT_INT,
394            CONTENT_NG_MISSING_DATA,
395            CONTENT_NG_EXTRA_DATA,
396            CONTENT_NG_EXTRA_ROW,
397        ] {
398            assert_eq!(
399                parse_stat(case.as_bytes()).unwrap_err().kind(),
400                ErrorKind::Parse
401            );
402        }
403
404        Ok(())
405    }
406}