cgroups_rs/
blkio.rs

1// Copyright (c) 2018 Levente Kurusa
2// Copyright (c) 2020 And Group
3//
4// SPDX-License-Identifier: Apache-2.0 or MIT
5//
6
7//! This module contains the implementation of the `blkio` cgroup subsystem.
8//!
9//! See the Kernel's documentation for more information about this subsystem, found at:
10//!  [Documentation/cgroup-v1/blkio-controller.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/blkio-controller.txt)
11use std::io::Write;
12use std::path::PathBuf;
13
14use crate::error::ErrorKind::*;
15use crate::error::*;
16
17use crate::{read_string_from, read_u64_from};
18use crate::{
19    BlkIoResources, ControllIdentifier, ControllerInternal, Controllers, CustomizedAttribute,
20    Resources, Subsystem,
21};
22
23/// A controller that allows controlling the `blkio` subsystem of a Cgroup.
24///
25/// In essence, using the `blkio` controller one can limit and throttle the tasks' usage of block
26/// devices in the control group.
27#[derive(Debug, Clone)]
28pub struct BlkIoController {
29    base: PathBuf,
30    path: PathBuf,
31    v2: bool,
32}
33
34#[derive(Eq, PartialEq, Debug)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36/// Per-device information
37pub struct BlkIoData {
38    /// The major number of the device.
39    pub major: i16,
40    /// The minor number of the device.
41    pub minor: i16,
42    /// The data that is associated with the device.
43    pub data: u64,
44}
45
46#[derive(Eq, PartialEq, Debug, Default)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48/// Per-device activity from the control group.
49pub struct IoService {
50    /// The major number of the device.
51    pub major: i16,
52    /// The minor number of the device.
53    pub minor: i16,
54    /// How many items were read from the device.
55    pub read: u64,
56    /// How many items were written to the device.
57    pub write: u64,
58    /// How many items were synchronously transferred.
59    pub sync: u64,
60    /// How many items were asynchronously transferred.
61    pub r#async: u64,
62    /// How many items were discarded.
63    pub discard: u64,
64    /// Total number of items transferred.
65    pub total: u64,
66}
67
68#[derive(Eq, PartialEq, Debug)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70/// Per-device activity from the control group.
71/// Only for cgroup v2
72pub struct IoStat {
73    /// The major number of the device.
74    pub major: i16,
75    /// The minor number of the device.
76    pub minor: i16,
77    /// How many bytes were read from the device.
78    pub rbytes: u64,
79    /// How many bytes were written to the device.
80    pub wbytes: u64,
81    /// How many iops were read from the device.
82    pub rios: u64,
83    /// How many iops were written to the device.
84    pub wios: u64,
85    /// How many discard bytes were read from the device.
86    pub dbytes: u64,
87    /// How many discard iops were written to the device.
88    pub dios: u64,
89}
90
91fn parse_io_service(s: String) -> Result<Vec<IoService>> {
92    let mut io_services = Vec::<IoService>::new();
93    let mut io_service = IoService::default();
94
95    let lines = s
96        .lines()
97        .filter(|x| x.split_whitespace().count() == 3)
98        .map(|x| {
99            let mut spl = x.split_whitespace();
100            (
101                spl.next().unwrap(),
102                spl.next().unwrap(),
103                spl.next().unwrap(),
104            )
105        })
106        .map(|(a, b, c)| {
107            let mut spl = a.split(':');
108            (
109                spl.next().unwrap().parse::<i16>(),
110                spl.next().unwrap().parse::<i16>(),
111                b,
112                c,
113            )
114        })
115        .collect::<Vec<_>>();
116
117    for (major_num, minor_num, op, val) in lines.iter() {
118        let major = *major_num.as_ref().map_err(|_| Error::new(ParseError))?;
119        let minor = *minor_num.as_ref().map_err(|_| Error::new(ParseError))?;
120
121        if (major != io_service.major || minor != io_service.minor) && io_service.major != 0 {
122            // new block device
123            io_services.push(io_service);
124            io_service = IoService::default();
125        }
126
127        io_service.major = major;
128        io_service.minor = minor;
129
130        let val = val.parse::<u64>().map_err(|_| Error::new(ParseError))?;
131
132        match *op {
133            "Read" => io_service.read = val,
134            "Write" => io_service.write = val,
135            "Sync" => io_service.sync = val,
136            "Async" => io_service.r#async = val,
137            "Discard" => io_service.discard = val,
138            "Total" => io_service.total = val,
139            _ => {}
140        }
141    }
142
143    if io_service.major != 0 {
144        io_services.push(io_service);
145    }
146
147    Ok(io_services)
148}
149
150fn get_value(s: &str) -> String {
151    let arr = s.split(':').collect::<Vec<&str>>();
152    if arr.len() != 2 {
153        return "0".to_string();
154    }
155    arr[1].to_string()
156}
157
158fn parse_io_stat(s: String) -> Vec<IoStat> {
159    // line:
160    // 8:0 rbytes=180224 wbytes=0 rios=3 wios=0 dbytes=0 dios=0
161    s.lines()
162        .filter(|x| x.split_whitespace().count() == 7)
163        .map(|x| {
164            let arr = x.split_whitespace().collect::<Vec<&str>>();
165            let device = arr[0].split(':').collect::<Vec<&str>>();
166            let (major, minor) = (device[0], device[1]);
167
168            IoStat {
169                major: major.parse::<i16>().unwrap(),
170                minor: minor.parse::<i16>().unwrap(),
171                rbytes: get_value(arr[1]).parse::<u64>().unwrap(),
172                wbytes: get_value(arr[2]).parse::<u64>().unwrap(),
173                rios: get_value(arr[3]).parse::<u64>().unwrap(),
174                wios: get_value(arr[4]).parse::<u64>().unwrap(),
175                dbytes: get_value(arr[5]).parse::<u64>().unwrap(),
176                dios: get_value(arr[6]).parse::<u64>().unwrap(),
177            }
178        })
179        .collect::<Vec<IoStat>>()
180}
181
182fn parse_io_service_total(s: String) -> Result<u64> {
183    s.lines()
184        .filter(|x| x.split_whitespace().count() == 2)
185        .fold(Err(Error::new(ParseError)), |_, x| {
186            match x.split_whitespace().collect::<Vec<_>>().as_slice() {
187                ["Total", val] => val.parse::<u64>().map_err(|_| Error::new(ParseError)),
188                _ => Err(Error::new(ParseError)),
189            }
190        })
191}
192
193fn parse_blkio_data(s: String) -> Result<Vec<BlkIoData>> {
194    let r = s
195        .chars()
196        .map(|x| if x == ':' { ' ' } else { x })
197        .collect::<String>();
198
199    let r = r
200        .lines()
201        .flat_map(|x| x.split_whitespace())
202        .collect::<Vec<_>>();
203
204    let r = r.chunks(3).collect::<Vec<_>>();
205
206    let mut res = Vec::new();
207
208    let err = r.iter().try_for_each(|x| match x {
209        [major, minor, data] => {
210            res.push(BlkIoData {
211                major: major.parse::<i16>().unwrap(),
212                minor: minor.parse::<i16>().unwrap(),
213                data: data.parse::<u64>().unwrap(),
214            });
215            Ok(())
216        }
217        _ => Err(Error::new(ParseError)),
218    });
219
220    if err.is_err() {
221        Err(Error::new(ParseError))
222    } else {
223        Ok(res)
224    }
225}
226
227/// Current state and statistics about how throttled are the block devices when accessed from the
228/// controller's control group.
229#[derive(Default, Debug)]
230#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
231pub struct BlkIoThrottle {
232    /// Statistics about the bytes transferred between the block devices by the tasks in this
233    /// control group.
234    pub io_service_bytes: Vec<IoService>,
235    /// Total amount of bytes transferred to and from the block devices.
236    pub io_service_bytes_total: u64,
237    /// Same as `io_service_bytes`, but contains all descendant control groups.
238    pub io_service_bytes_recursive: Vec<IoService>,
239    /// Total amount of bytes transferred to and from the block devices, including all descendant
240    /// control groups.
241    pub io_service_bytes_recursive_total: u64,
242    /// The number of I/O operations performed on the devices as seen by the throttling policy.
243    pub io_serviced: Vec<IoService>,
244    /// The total number of I/O operations performed on the devices as seen by the throttling
245    /// policy.
246    pub io_serviced_total: u64,
247    /// Same as `io_serviced`, but contains all descendant control groups.
248    pub io_serviced_recursive: Vec<IoService>,
249    /// Same as `io_serviced`, but contains all descendant control groups and contains only the
250    /// total amount.
251    pub io_serviced_recursive_total: u64,
252    /// The upper limit of bytes per second rate of read operation on the block devices by the
253    /// control group's tasks.
254    pub read_bps_device: Vec<BlkIoData>,
255    /// The upper limit of I/O operation per second, when said operation is a read operation.
256    pub read_iops_device: Vec<BlkIoData>,
257    /// The upper limit of bytes per second rate of write operation on the block devices by the
258    /// control group's tasks.
259    pub write_bps_device: Vec<BlkIoData>,
260    /// The upper limit of I/O operation per second, when said operation is a write operation.
261    pub write_iops_device: Vec<BlkIoData>,
262}
263
264/// Statistics and state of the block devices.
265#[derive(Default, Debug)]
266#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
267pub struct BlkIo {
268    /// The number of BIOS requests merged into I/O requests by the control group's tasks.
269    pub io_merged: Vec<IoService>,
270    /// Same as `io_merged`, but only reports the total number.
271    pub io_merged_total: u64,
272    /// Same as `io_merged`, but contains all descendant control groups.
273    pub io_merged_recursive: Vec<IoService>,
274    /// Same as `io_merged_recursive`, but only reports the total number.
275    pub io_merged_recursive_total: u64,
276    /// The number of requests queued for I/O operations by the tasks of the control group.
277    pub io_queued: Vec<IoService>,
278    /// Same as `io_queued`, but only reports the total number.
279    pub io_queued_total: u64,
280    /// Same as `io_queued`, but contains all descendant control groups.
281    pub io_queued_recursive: Vec<IoService>,
282    /// Same as `io_queued_recursive`, but contains all descendant control groups.
283    pub io_queued_recursive_total: u64,
284    /// The number of bytes transferred from and to the block device (as seen by the CFQ I/O scheduler).
285    pub io_service_bytes: Vec<IoService>,
286    /// Same as `io_service_bytes`, but contains all descendant control groups.
287    pub io_service_bytes_total: u64,
288    /// Same as `io_service_bytes`, but contains all descendant control groups.
289    pub io_service_bytes_recursive: Vec<IoService>,
290    /// Total amount of bytes transferred between the tasks and block devices, including the
291    /// descendant control groups' numbers.
292    pub io_service_bytes_recursive_total: u64,
293    /// The number of I/O operations (as seen by the CFQ I/O scheduler) between the devices and the
294    /// control group's tasks.
295    pub io_serviced: Vec<IoService>,
296    /// The total number of I/O operations performed on the devices as seen by the throttling
297    /// policy.
298    pub io_serviced_total: u64,
299    /// Same as `io_serviced`, but contains all descendant control groups.
300    pub io_serviced_recursive: Vec<IoService>,
301    /// Same as `io_serviced`, but contains all descendant control groups and contains only the
302    /// total amount.
303    pub io_serviced_recursive_total: u64,
304    /// The total time spent between dispatch and request completion for I/O requests (as seen by
305    /// the CFQ I/O scheduler) by the control group's tasks.
306    pub io_service_time: Vec<IoService>,
307    /// Same as `io_service_time`, but contains all descendant control groups and contains only the
308    /// total amount.
309    pub io_service_time_total: u64,
310    /// Same as `io_service_time`, but contains all descendant control groups.
311    pub io_service_time_recursive: Vec<IoService>,
312    /// Same as `io_service_time_recursive`, but contains all descendant control groups and only
313    /// the total amount.
314    pub io_service_time_recursive_total: u64,
315    /// Total amount of time spent waiting for a free slot in the CFQ I/O scheduler's queue.
316    pub io_wait_time: Vec<IoService>,
317    /// Same as `io_wait_time`, but only reports the total amount.
318    pub io_wait_time_total: u64,
319    /// Same as `io_wait_time`, but contains all descendant control groups.
320    pub io_wait_time_recursive: Vec<IoService>,
321    /// Same as `io_wait_time_recursive`, but only reports the total amount.
322    pub io_wait_time_recursive_total: u64,
323    /// How much weight do the control group's tasks have when competing against the descendant
324    /// control group's tasks.
325    pub leaf_weight: u64,
326    /// Same as `leaf_weight`, but per-block-device.
327    pub leaf_weight_device: Vec<BlkIoData>,
328    /// Total number of sectors transferred between the block devices and the control group's
329    /// tasks.
330    pub sectors: Vec<BlkIoData>,
331    /// Same as `sectors`, but contains all descendant control groups.
332    pub sectors_recursive: Vec<BlkIoData>,
333    /// Similar statistics, but as seen by the throttle policy.
334    pub throttle: BlkIoThrottle,
335    /// The time the control group had access to the I/O devices.
336    pub time: Vec<BlkIoData>,
337    /// Same as `time`, but contains all descendant control groups.
338    pub time_recursive: Vec<BlkIoData>,
339    /// The weight of this control group.
340    pub weight: u64,
341    /// Same as `weight`, but per-block-device.
342    pub weight_device: Vec<BlkIoData>,
343
344    /// IoStat for cgroup v2
345    pub io_stat: Vec<IoStat>,
346}
347
348impl ControllerInternal for BlkIoController {
349    fn control_type(&self) -> Controllers {
350        Controllers::BlkIo
351    }
352    fn get_path(&self) -> &PathBuf {
353        &self.path
354    }
355    fn get_path_mut(&mut self) -> &mut PathBuf {
356        &mut self.path
357    }
358    fn get_base(&self) -> &PathBuf {
359        &self.base
360    }
361
362    fn is_v2(&self) -> bool {
363        self.v2
364    }
365
366    fn apply(&self, res: &Resources) -> Result<()> {
367        // get the resources that apply to this controller
368        let res: &BlkIoResources = &res.blkio;
369
370        if let Some(weight) = res.weight {
371            let _ = self.set_weight(weight as u64);
372        }
373        if let Some(leaf_weight) = res.leaf_weight {
374            let _ = self.set_leaf_weight(leaf_weight as u64);
375        }
376
377        for dev in &res.weight_device {
378            if let Some(weight) = dev.weight {
379                let _ = self.set_weight_for_device(dev.major, dev.minor, weight as u64);
380            }
381            if let Some(leaf_weight) = dev.leaf_weight {
382                let _ = self.set_leaf_weight_for_device(dev.major, dev.minor, leaf_weight as u64);
383            }
384        }
385
386        for dev in &res.throttle_read_bps_device {
387            let _ = self.throttle_read_bps_for_device(dev.major, dev.minor, dev.rate);
388        }
389
390        for dev in &res.throttle_write_bps_device {
391            let _ = self.throttle_write_bps_for_device(dev.major, dev.minor, dev.rate);
392        }
393
394        for dev in &res.throttle_read_iops_device {
395            let _ = self.throttle_read_iops_for_device(dev.major, dev.minor, dev.rate);
396        }
397
398        for dev in &res.throttle_write_iops_device {
399            let _ = self.throttle_write_iops_for_device(dev.major, dev.minor, dev.rate);
400        }
401
402        res.attrs.iter().for_each(|(k, v)| {
403            let _ = self.set(k, v);
404        });
405
406        Ok(())
407    }
408}
409
410impl ControllIdentifier for BlkIoController {
411    fn controller_type() -> Controllers {
412        Controllers::BlkIo
413    }
414}
415
416impl<'a> From<&'a Subsystem> for &'a BlkIoController {
417    fn from(sub: &'a Subsystem) -> &'a BlkIoController {
418        unsafe {
419            match sub {
420                Subsystem::BlkIo(c) => c,
421                _ => {
422                    assert_eq!(1, 0);
423                    let v = std::mem::MaybeUninit::uninit();
424                    v.assume_init()
425                }
426            }
427        }
428    }
429}
430
431impl BlkIoController {
432    /// Constructs a new `BlkIoController` with `root` serving as the root of the control group.
433    pub fn new(root: PathBuf, v2: bool) -> Self {
434        Self {
435            base: root.clone(),
436            path: root,
437            v2,
438        }
439    }
440
441    fn blkio_v2(&self) -> BlkIo {
442        BlkIo {
443            io_stat: self
444                .open_path("io.stat", false)
445                .and_then(read_string_from)
446                .map(parse_io_stat)
447                .unwrap_or_default(),
448            ..Default::default()
449        }
450    }
451
452    /// Gathers statistics about and reports the state of the block devices used by the control
453    /// group's tasks.
454    pub fn blkio(&self) -> BlkIo {
455        if self.v2 {
456            return self.blkio_v2();
457        }
458        BlkIo {
459            io_merged: self
460                .open_path("blkio.io_merged", false)
461                .and_then(read_string_from)
462                .and_then(parse_io_service)
463                .unwrap_or_default(),
464            io_merged_total: self
465                .open_path("blkio.io_merged", false)
466                .and_then(read_string_from)
467                .and_then(parse_io_service_total)
468                .unwrap_or_default(),
469            io_merged_recursive: self
470                .open_path("blkio.io_merged_recursive", false)
471                .and_then(read_string_from)
472                .and_then(parse_io_service)
473                .unwrap_or_default(),
474            io_merged_recursive_total: self
475                .open_path("blkio.io_merged_recursive", false)
476                .and_then(read_string_from)
477                .and_then(parse_io_service_total)
478                .unwrap_or_default(),
479            io_queued: self
480                .open_path("blkio.io_queued", false)
481                .and_then(read_string_from)
482                .and_then(parse_io_service)
483                .unwrap_or_default(),
484            io_queued_total: self
485                .open_path("blkio.io_queued", false)
486                .and_then(read_string_from)
487                .and_then(parse_io_service_total)
488                .unwrap_or_default(),
489            io_queued_recursive: self
490                .open_path("blkio.io_queued_recursive", false)
491                .and_then(read_string_from)
492                .and_then(parse_io_service)
493                .unwrap_or_default(),
494            io_queued_recursive_total: self
495                .open_path("blkio.io_queued_recursive", false)
496                .and_then(read_string_from)
497                .and_then(parse_io_service_total)
498                .unwrap_or_default(),
499            io_service_bytes: self
500                .open_path("blkio.io_service_bytes", false)
501                .and_then(read_string_from)
502                .and_then(parse_io_service)
503                .unwrap_or_default(),
504            io_service_bytes_total: self
505                .open_path("blkio.io_service_bytes", false)
506                .and_then(read_string_from)
507                .and_then(parse_io_service_total)
508                .unwrap_or_default(),
509            io_service_bytes_recursive: self
510                .open_path("blkio.io_service_bytes_recursive", false)
511                .and_then(read_string_from)
512                .and_then(parse_io_service)
513                .unwrap_or_default(),
514            io_service_bytes_recursive_total: self
515                .open_path("blkio.io_service_bytes_recursive", false)
516                .and_then(read_string_from)
517                .and_then(parse_io_service_total)
518                .unwrap_or_default(),
519            io_serviced: self
520                .open_path("blkio.io_serviced", false)
521                .and_then(read_string_from)
522                .and_then(parse_io_service)
523                .unwrap_or_default(),
524            io_serviced_total: self
525                .open_path("blkio.io_serviced", false)
526                .and_then(read_string_from)
527                .and_then(parse_io_service_total)
528                .unwrap_or_default(),
529            io_serviced_recursive: self
530                .open_path("blkio.io_serviced_recursive", false)
531                .and_then(read_string_from)
532                .and_then(parse_io_service)
533                .unwrap_or_default(),
534            io_serviced_recursive_total: self
535                .open_path("blkio.io_serviced_recursive", false)
536                .and_then(read_string_from)
537                .and_then(parse_io_service_total)
538                .unwrap_or_default(),
539            io_service_time: self
540                .open_path("blkio.io_service_time", false)
541                .and_then(read_string_from)
542                .and_then(parse_io_service)
543                .unwrap_or_default(),
544            io_service_time_total: self
545                .open_path("blkio.io_service_time", false)
546                .and_then(read_string_from)
547                .and_then(parse_io_service_total)
548                .unwrap_or_default(),
549            io_service_time_recursive: self
550                .open_path("blkio.io_service_time_recursive", false)
551                .and_then(read_string_from)
552                .and_then(parse_io_service)
553                .unwrap_or_default(),
554            io_service_time_recursive_total: self
555                .open_path("blkio.io_service_time_recursive", false)
556                .and_then(read_string_from)
557                .and_then(parse_io_service_total)
558                .unwrap_or_default(),
559            io_wait_time: self
560                .open_path("blkio.io_wait_time", false)
561                .and_then(read_string_from)
562                .and_then(parse_io_service)
563                .unwrap_or_default(),
564            io_wait_time_total: self
565                .open_path("blkio.io_wait_time", false)
566                .and_then(read_string_from)
567                .and_then(parse_io_service_total)
568                .unwrap_or_default(),
569            io_wait_time_recursive: self
570                .open_path("blkio.io_wait_time_recursive", false)
571                .and_then(read_string_from)
572                .and_then(parse_io_service)
573                .unwrap_or_default(),
574            io_wait_time_recursive_total: self
575                .open_path("blkio.io_wait_time_recursive", false)
576                .and_then(read_string_from)
577                .and_then(parse_io_service_total)
578                .unwrap_or_default(),
579            leaf_weight: self
580                .open_path("blkio.leaf_weight", false)
581                .and_then(read_u64_from)
582                .unwrap_or(0u64),
583            leaf_weight_device: self
584                .open_path("blkio.leaf_weight_device", false)
585                .and_then(read_string_from)
586                .and_then(parse_blkio_data)
587                .unwrap_or_default(),
588            sectors: self
589                .open_path("blkio.sectors", false)
590                .and_then(read_string_from)
591                .and_then(parse_blkio_data)
592                .unwrap_or_default(),
593            sectors_recursive: self
594                .open_path("blkio.sectors_recursive", false)
595                .and_then(read_string_from)
596                .and_then(parse_blkio_data)
597                .unwrap_or_default(),
598            throttle: BlkIoThrottle {
599                io_service_bytes: self
600                    .open_path("blkio.throttle.io_service_bytes", false)
601                    .and_then(read_string_from)
602                    .and_then(parse_io_service)
603                    .unwrap_or_default(),
604                io_service_bytes_total: self
605                    .open_path("blkio.throttle.io_service_bytes", false)
606                    .and_then(read_string_from)
607                    .and_then(parse_io_service_total)
608                    .unwrap_or_default(),
609                io_service_bytes_recursive: self
610                    .open_path("blkio.throttle.io_service_bytes_recursive", false)
611                    .and_then(read_string_from)
612                    .and_then(parse_io_service)
613                    .unwrap_or_default(),
614                io_service_bytes_recursive_total: self
615                    .open_path("blkio.throttle.io_service_bytes_recursive", false)
616                    .and_then(read_string_from)
617                    .and_then(parse_io_service_total)
618                    .unwrap_or_default(),
619                io_serviced: self
620                    .open_path("blkio.throttle.io_serviced", false)
621                    .and_then(read_string_from)
622                    .and_then(parse_io_service)
623                    .unwrap_or_default(),
624                io_serviced_total: self
625                    .open_path("blkio.throttle.io_serviced", false)
626                    .and_then(read_string_from)
627                    .and_then(parse_io_service_total)
628                    .unwrap_or_default(),
629                io_serviced_recursive: self
630                    .open_path("blkio.throttle.io_serviced_recursive", false)
631                    .and_then(read_string_from)
632                    .and_then(parse_io_service)
633                    .unwrap_or_default(),
634                io_serviced_recursive_total: self
635                    .open_path("blkio.throttle.io_serviced_recursive", false)
636                    .and_then(read_string_from)
637                    .and_then(parse_io_service_total)
638                    .unwrap_or_default(),
639                read_bps_device: self
640                    .open_path("blkio.throttle.read_bps_device", false)
641                    .and_then(read_string_from)
642                    .and_then(parse_blkio_data)
643                    .unwrap_or_default(),
644                read_iops_device: self
645                    .open_path("blkio.throttle.read_iops_device", false)
646                    .and_then(read_string_from)
647                    .and_then(parse_blkio_data)
648                    .unwrap_or_default(),
649                write_bps_device: self
650                    .open_path("blkio.throttle.write_bps_device", false)
651                    .and_then(read_string_from)
652                    .and_then(parse_blkio_data)
653                    .unwrap_or_default(),
654                write_iops_device: self
655                    .open_path("blkio.throttle.write_iops_device", false)
656                    .and_then(read_string_from)
657                    .and_then(parse_blkio_data)
658                    .unwrap_or_default(),
659            },
660            time: self
661                .open_path("blkio.time", false)
662                .and_then(read_string_from)
663                .and_then(parse_blkio_data)
664                .unwrap_or_default(),
665            time_recursive: self
666                .open_path("blkio.time_recursive", false)
667                .and_then(read_string_from)
668                .and_then(parse_blkio_data)
669                .unwrap_or_default(),
670            weight: self
671                .open_path("blkio.weight", false)
672                .and_then(read_u64_from)
673                .unwrap_or(0u64),
674            weight_device: self
675                .open_path("blkio.weight_device", false)
676                .and_then(read_string_from)
677                .and_then(parse_blkio_data)
678                .unwrap_or_default(),
679            io_stat: Vec::new(),
680        }
681    }
682
683    /// Set the leaf weight on the control group's tasks, i.e., how are they weighted against the
684    /// descendant control groups' tasks.
685    pub fn set_leaf_weight(&self, w: u64) -> Result<()> {
686        self.open_path("blkio.leaf_weight", true)
687            .and_then(|mut file| {
688                file.write_all(w.to_string().as_ref()).map_err(|e| {
689                    Error::with_cause(
690                        WriteFailed("blkio.leaf_weight".to_string(), w.to_string()),
691                        e,
692                    )
693                })
694            })
695    }
696
697    /// Same as `set_leaf_weight()`, but settable per each block device.
698    pub fn set_leaf_weight_for_device(&self, major: u64, minor: u64, weight: u64) -> Result<()> {
699        self.open_path("blkio.leaf_weight_device", true)
700            .and_then(|mut file| {
701                file.write_all(format!("{}:{} {}", major, minor, weight).as_ref())
702                    .map_err(|e| {
703                        Error::with_cause(
704                            WriteFailed(
705                                "blkio.leaf_weight_device".to_string(),
706                                format!("{}:{} {}", major, minor, weight),
707                            ),
708                            e,
709                        )
710                    })
711            })
712    }
713
714    /// Reset the statistics the kernel has gathered so far and start fresh.
715    pub fn reset_stats(&self) -> Result<()> {
716        self.open_path("blkio.reset_stats", true)
717            .and_then(|mut file| {
718                file.write_all("1".to_string().as_ref()).map_err(|e| {
719                    Error::with_cause(
720                        WriteFailed("blkio.reset_stats".to_string(), "1".to_string()),
721                        e,
722                    )
723                })
724            })
725    }
726
727    /// Throttle the bytes per second rate of read operation affecting the block device
728    /// `major:minor` to `bps`.
729    pub fn throttle_read_bps_for_device(&self, major: u64, minor: u64, bps: u64) -> Result<()> {
730        let mut file_name = "blkio.throttle.read_bps_device";
731        let mut content = format!("{}:{} {}", major, minor, bps);
732        if self.v2 {
733            file_name = "io.max";
734            content = format!("{}:{} rbps={}", major, minor, bps);
735        }
736        self.open_path(file_name, true).and_then(|mut file| {
737            file.write_all(content.as_ref()).map_err(|e| {
738                Error::with_cause(WriteFailed(file_name.to_string(), content.to_string()), e)
739            })
740        })
741    }
742
743    /// Throttle the I/O operations per second rate of read operation affecting the block device
744    /// `major:minor` to `bps`.
745    pub fn throttle_read_iops_for_device(&self, major: u64, minor: u64, iops: u64) -> Result<()> {
746        let mut file_name = "blkio.throttle.read_iops_device";
747        let mut content = format!("{}:{} {}", major, minor, iops);
748        if self.v2 {
749            file_name = "io.max";
750            content = format!("{}:{} riops={}", major, minor, iops);
751        }
752        self.open_path(file_name, true).and_then(|mut file| {
753            file.write_all(content.as_ref()).map_err(|e| {
754                Error::with_cause(WriteFailed(file_name.to_string(), content.to_string()), e)
755            })
756        })
757    }
758    /// Throttle the bytes per second rate of write operation affecting the block device
759    /// `major:minor` to `bps`.
760    pub fn throttle_write_bps_for_device(&self, major: u64, minor: u64, bps: u64) -> Result<()> {
761        let mut file_name = "blkio.throttle.write_bps_device";
762        let mut content = format!("{}:{} {}", major, minor, bps);
763        if self.v2 {
764            file_name = "io.max";
765            content = format!("{}:{} wbps={}", major, minor, bps);
766        }
767        self.open_path(file_name, true).and_then(|mut file| {
768            file.write_all(content.as_ref()).map_err(|e| {
769                Error::with_cause(WriteFailed(file_name.to_string(), content.to_string()), e)
770            })
771        })
772    }
773
774    /// Throttle the I/O operations per second rate of write operation affecting the block device
775    /// `major:minor` to `bps`.
776    pub fn throttle_write_iops_for_device(&self, major: u64, minor: u64, iops: u64) -> Result<()> {
777        let mut file_name = "blkio.throttle.write_iops_device";
778        let mut content = format!("{}:{} {}", major, minor, iops);
779        if self.v2 {
780            file_name = "io.max";
781            content = format!("{}:{} wiops={}", major, minor, iops);
782        }
783        self.open_path(file_name, true).and_then(|mut file| {
784            file.write_all(content.as_ref()).map_err(|e| {
785                Error::with_cause(WriteFailed(file_name.to_string(), content.to_string()), e)
786            })
787        })
788    }
789
790    /// Set the weight of the control group's tasks.
791    pub fn set_weight(&self, w: u64) -> Result<()> {
792        // Attation: may not find in high kernel version.
793        let mut file_name = "blkio.weight";
794        if self.v2 {
795            file_name = "io.bfq.weight";
796        }
797        self.open_path(file_name, true).and_then(|mut file| {
798            file.write_all(w.to_string().as_ref()).map_err(|e| {
799                Error::with_cause(WriteFailed(file_name.to_string(), w.to_string()), e)
800            })
801        })
802    }
803
804    /// Same as `set_weight()`, but settable per each block device.
805    pub fn set_weight_for_device(&self, major: u64, minor: u64, weight: u64) -> Result<()> {
806        let mut file_name = "blkio.weight_device";
807        if self.v2 {
808            // Attation: there is no weight for device in runc
809            // https://github.com/opencontainers/runc/blob/46be7b612e2533c494e6a251111de46d8e286ed5/libcontainer/cgroups/fs2/io.go#L30
810            // may depends on IO schedulers https://wiki.ubuntu.com/Kernel/Reference/IOSchedulers
811            file_name = "io.bfq.weight";
812        }
813        self.open_path(file_name, true).and_then(|mut file| {
814            file.write_all(format!("{}:{} {}", major, minor, weight).as_ref())
815                .map_err(|e| {
816                    Error::with_cause(
817                        WriteFailed(
818                            file_name.to_string(),
819                            format!("{}:{} {}", major, minor, weight),
820                        ),
821                        e,
822                    )
823                })
824        })
825    }
826}
827
828impl CustomizedAttribute for BlkIoController {}
829#[cfg(test)]
830mod test {
831    use crate::blkio::{parse_blkio_data, BlkIoData};
832    use crate::blkio::{parse_io_service, parse_io_service_total, IoService};
833    use crate::error::*;
834
835    static TEST_VALUE: &str = "\
8368:32 Read 4280320
8378:32 Write 0
8388:32 Sync 4280320
8398:32 Async 0
8408:32 Discard 1
8418:32 Total 4280320
8428:48 Read 5705479168
8438:48 Write 56096055296
8448:48 Sync 11213923328
8458:48 Async 50587611136
8468:48 Total 61801534464
8478:16 Read 10059776
8488:16 Write 0
8498:16 Sync 10059776
8508:16 Async 0
8518:16 Total 10059776
8528:0 Read 7192576
8538:0 Write 0
8548:0 Sync 7192576
8558:0 Async 0
8568:0 Total 7192576
857Total 61823067136
858 ";
859
860    static TEST_BLKIO_DATA: &str = "\
8618:48 454480833999
8628:32 228392923193
8638:16 772456885
8648:0 559583764
865 ";
866
867    #[test]
868    fn test_parse_io_service_total() {
869        let ok = parse_io_service_total(TEST_VALUE.to_string()).unwrap();
870        assert_eq!(ok, 61823067136);
871    }
872
873    #[test]
874    fn test_parse_io_service() {
875        let ok = parse_io_service(TEST_VALUE.to_string()).unwrap();
876        assert_eq!(
877            ok,
878            vec![
879                IoService {
880                    major: 8,
881                    minor: 32,
882                    read: 4280320,
883                    write: 0,
884                    sync: 4280320,
885                    r#async: 0,
886                    discard: 1,
887                    total: 4280320,
888                },
889                IoService {
890                    major: 8,
891                    minor: 48,
892                    read: 5705479168,
893                    write: 56096055296,
894                    sync: 11213923328,
895                    r#async: 50587611136,
896                    discard: 0,
897                    total: 61801534464,
898                },
899                IoService {
900                    major: 8,
901                    minor: 16,
902                    read: 10059776,
903                    write: 0,
904                    sync: 10059776,
905                    r#async: 0,
906                    discard: 0,
907                    total: 10059776,
908                },
909                IoService {
910                    major: 8,
911                    minor: 0,
912                    read: 7192576,
913                    write: 0,
914                    sync: 7192576,
915                    r#async: 0,
916                    discard: 0,
917                    total: 7192576,
918                }
919            ]
920        );
921
922        let invalid_values = vec![
923            "\
9248:32 Read 4280320
9258:32 Write a
9268:32 Async 1
927",
928            "\
9298:32 Read 4280320
930b:32 Write 1
9318:32 Async 1
932",
933            "\
9348:32 Read 4280320
9358:32 Write 1
9368:c Async 1
937",
938        ];
939
940        for value in invalid_values {
941            let err = parse_io_service(value.to_string()).unwrap_err();
942            assert_eq!(err.kind(), &ErrorKind::ParseError,);
943        }
944    }
945
946    #[test]
947    fn test_parse_blkio_data() {
948        assert_eq!(
949            parse_blkio_data(TEST_BLKIO_DATA.to_string()).unwrap(),
950            vec![
951                BlkIoData {
952                    major: 8,
953                    minor: 48,
954                    data: 454480833999,
955                },
956                BlkIoData {
957                    major: 8,
958                    minor: 32,
959                    data: 228392923193,
960                },
961                BlkIoData {
962                    major: 8,
963                    minor: 16,
964                    data: 772456885,
965                },
966                BlkIoData {
967                    major: 8,
968                    minor: 0,
969                    data: 559583764,
970                }
971            ]
972        );
973    }
974}