1use std::path::PathBuf;
53
54use crate::{
55 parse::{parse, parse_next},
56 v1::{self, cgroup::CgroupHelper, Cgroup, CgroupPath},
57 Result,
58};
59
60#[derive(Debug)]
62pub struct Subsystem {
63 path: CgroupPath,
64}
65
66#[derive(Debug, Default, Clone, PartialEq, Eq)]
70pub struct Resources {
71 pub shares: Option<u64>,
73 pub cfs_quota_us: Option<i64>,
77 pub cfs_period_us: Option<u64>,
79
80 pub rt_runtime_us: Option<i64>,
84 pub rt_period_us: Option<u64>,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct Stat {
91 pub nr_periods: u64,
95 pub nr_throttled: u64,
97 pub throttled_time: u64,
99}
100
101impl_cgroup! {
102 Subsystem, Cpu,
103
104 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] 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)?; crate::consume_cpu_until(|| cgroup.stat().unwrap().nr_throttled > 0, 30);
315 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}