Skip to main content

controlgroup/v1/
rdma.rs

1//! Operations on an RDMA 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/cgroup-v1/rdma.txt].
7//!
8//! # Examples
9//!
10//! ```no_run
11//! # fn main() -> controlgroup::Result<()> {
12//! use std::{collections::HashMap, path::PathBuf};
13//! use controlgroup::{Pid, Max, v1::{self, rdma, Cgroup, CgroupPath, SubsystemKind}};
14//!
15//! let mut rdma_cgroup = rdma::Subsystem::new(
16//!     CgroupPath::new(SubsystemKind::Rdma, PathBuf::from("students/charlie")));
17//! rdma_cgroup.create()?;
18//!
19//! // Limit the usage of RDMA/IB devices.
20//! let rdma_limits = [
21//!         (
22//!             "mlx4_0",
23//!             rdma::Limit {
24//!                 hca_handle: 2.into(),
25//!                 hca_object: 2000.into(),
26//!             },
27//!         ),
28//!         (
29//!             "ocrdma1",
30//!             rdma::Limit {
31//!                 hca_handle: 3.into(),
32//!                 hca_object: Max::Max,
33//!             },
34//!         ),
35//!     ];
36//!
37//! rdma_cgroup.set_max(rdma_limits.iter())?;
38//!
39//! // Add tasks to this cgroup.
40//! let pid = Pid::from(std::process::id());
41//! rdma_cgroup.add_task(pid)?;
42//!
43//! // Do something ...
44//!
45//! // Print the current usage of RDMA/IB devices.
46//! for (device, usage) in rdma_cgroup.current()? {
47//!     println!("{}: {}", device, usage);
48//! }
49//!
50//! rdma_cgroup.remove_task(pid)?;
51//! rdma_cgroup.delete()?;
52//! # Ok(())
53//! # }
54//! ```
55//!
56//! [`Subsystem`]: struct.Subsystem.html
57//! [`Cgroup`]: ../trait.Cgroup.html
58//!
59//! [Documentation/cgroup-v1/rdma.txt]: https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt
60
61use std::{collections::HashMap, fmt, path::PathBuf};
62
63use crate::{
64    parse::parse_next,
65    v1::{self, Cgroup, CgroupPath},
66    Error, ErrorKind, Max, Result,
67};
68
69/// Handler of an RDMA subsystem.
70#[derive(Debug)]
71pub struct Subsystem {
72    path: CgroupPath,
73}
74
75/// Resource limit on how much a cgroup can use RDMA/IB devices.
76///
77/// See the kernel's documentation for more information about the fields.
78#[derive(Debug, Default, Clone, PartialEq, Eq)]
79pub struct Resources {
80    /// How much this cgroup can use each RDMA/IB device. The key is the device name, and the value
81    /// is limit for the device.
82    ///
83    /// No limits will be applied if this map is empty.
84    pub max: HashMap<String, Limit>,
85}
86
87/// Limit or usage of an RDMA/IB device.
88#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
89pub struct Limit {
90    /// Max number or usage of HCA handles.
91    pub hca_handle: Max,
92    /// Max number or usage of HCA objects.
93    pub hca_object: Max,
94}
95
96impl_cgroup! {
97    Subsystem, Rdma,
98
99    /// Applies `resources.rdma.max` if it is not empty.
100    fn apply(&mut self, resources: &v1::Resources) -> Result<()> {
101        let max = &resources.rdma.max;
102
103        if max.is_empty() {
104            Ok(())
105        } else {
106            self.set_max(max.iter())
107        }
108    }
109}
110
111impl Subsystem {
112    gen_getter!(
113        rdma, "the current usage of RDMA/IB devices",
114        current, HashMap<String, Limit>, parse_limits
115    );
116
117    gen_getter!(
118        rdma, "the usage limits on RDMA/IB devices",
119        max : link, HashMap<String, Limit>, parse_limits
120    );
121
122    with_doc! { concat!(
123        gen_doc!(
124            sets; "rdma.max",
125            "usage limits on RDMA/IB devices"
126             : "The first element of the iterator item is device name,
127                and the second is limit for the device."
128        ),
129        gen_doc!(see; max),
130        gen_doc!(err_write; "rdma.max"),
131        gen_doc!(
132            eg_write;
133            rdma,
134            set_max,
135            [(
136                "mlx4_0",
137                rdma::Limit { hca_handle: 3.into(), hca_object: controlgroup::Max::Max }
138            )].iter()
139        )),
140        pub fn set_max<I, T, K>(&mut self, limits: I) -> Result<()>
141        where
142            I: Iterator<Item = T>,
143            T: crate::RefKv<K, Limit>,
144            K: fmt::Display,
145        {
146            use std::io::Write;
147
148            let mut file = self.open_file_write("rdma.max")?;
149            for lim in limits {
150                let (device, limit) = lim.ref_kv();
151
152                // write!(file, "{} {}", interface, prio)?; // not work
153                file.write_all(format!("{} {}", device, limit).as_bytes())?;
154            }
155
156            Ok(())
157        }
158    }
159}
160
161fn parse_limits(reader: impl std::io::Read) -> Result<HashMap<String, Limit>> {
162    use std::io::{BufRead, BufReader};
163
164    let mut result = HashMap::new();
165    let buf = BufReader::new(reader);
166
167    for line in buf.lines() {
168        let line = line?;
169        let mut entry = line.split_whitespace();
170
171        let device = entry.next().ok_or_else(|| Error::new(ErrorKind::Parse))?;
172
173        let (mut hca_handle, mut hca_object) = (None, None);
174        for e in entry.by_ref().take(2) {
175            let mut kv = e.split('=');
176
177            match kv.next() {
178                // FIXME: is column order guaranteed?
179                Some("hca_handle") => {
180                    if hca_handle.is_some() {
181                        bail_parse!();
182                    }
183                    hca_handle = Some(parse_next(kv)?);
184                }
185                Some("hca_object") => {
186                    if hca_object.is_some() {
187                        bail_parse!();
188                    }
189                    hca_object = Some(parse_next(kv)?);
190                }
191                _ => {
192                    bail_parse!();
193                }
194            }
195        }
196
197        match (hca_handle, hca_object, entry.next()) {
198            (Some(hca_handle), Some(hca_object), None) => {
199                result.insert(
200                    device.to_string(),
201                    Limit {
202                        hca_handle,
203                        hca_object,
204                    },
205                );
206            }
207            _ => {
208                bail_parse!();
209            }
210        }
211    }
212
213    Ok(result)
214}
215
216impl Into<v1::Resources> for Resources {
217    fn into(self) -> v1::Resources {
218        v1::Resources {
219            rdma: self,
220            ..v1::Resources::default()
221        }
222    }
223}
224
225impl fmt::Display for Limit {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        write!(
228            f,
229            "hca_handle={} hca_object={}",
230            self.hca_handle, self.hca_object
231        )
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238    use v1::SubsystemKind;
239
240    #[test]
241    #[ignore] // some systems have no RDMA/IB devices
242    fn test_subsystem_create_file_exists() -> Result<()> {
243        gen_subsystem_test!(Rdma, ["current", "max"])
244    }
245
246    #[test]
247    fn test_subsystem_apply() -> Result<()> {
248        //
249
250        Ok(())
251    }
252
253    #[test]
254    #[ignore] // some systems have no RDMA/IB devices
255    fn test_subsystem_current() -> Result<()> {
256        let mut cgroup = Subsystem::new(CgroupPath::new(SubsystemKind::Rdma, gen_cgroup_name!()));
257        cgroup.create()?;
258
259        let _ = cgroup.current()?;
260
261        cgroup.delete()
262    }
263
264    #[test]
265    #[ignore] // some systems have no RDMA/IB devices
266    fn test_subsystem_max() -> Result<()> {
267        let mut cgroup = Subsystem::new(CgroupPath::new(SubsystemKind::Rdma, gen_cgroup_name!()));
268        cgroup.create()?;
269
270        let mut limits = cgroup.max()?;
271        for (_, limit) in limits.iter_mut() {
272            limit.hca_handle = match limit.hca_handle {
273                Max::Max => Max::Limit(3),
274                Max::Limit(_) => Max::Max,
275            };
276
277            limit.hca_object = match limit.hca_object {
278                Max::Max => Max::Limit(3000),
279                Max::Limit(_) => Max::Max,
280            };
281        }
282
283        cgroup.set_max(limits.iter())?;
284        assert_eq!(cgroup.max()?, limits);
285
286        cgroup.delete()
287    }
288
289    #[test]
290    fn test_parse_limits() -> Result<()> {
291        const CONTENT_OK_0: &str = "\
292mlx4_0 hca_handle=2 hca_object=2000
293ocrdma1 hca_handle=3 hca_object=max
294";
295
296        const CONTENT_OK_1: &str = "\
297mlx4_0 hca_object=2000 hca_handle=2
298ocrdma1 hca_object=max hca_handle=3
299";
300
301        let expected = hashmap! {
302            (
303                "mlx4_0".to_string(),
304                Limit {
305                    hca_handle: Max::Limit(2),
306                    hca_object: Max::Limit(2000),
307                },
308            ),
309            (
310                "ocrdma1".to_string(),
311                Limit {
312                    hca_handle: Max::Limit(3),
313                    hca_object: Max::Max,
314                },
315            ),
316        };
317
318        assert_eq!(parse_limits(CONTENT_OK_0.as_bytes())?, expected);
319        assert_eq!(parse_limits(CONTENT_OK_1.as_bytes())?, expected);
320
321        assert!(parse_limits("".as_bytes())?.is_empty());
322
323        const CONTENT_NG_NOT_INT: &str = "\
324mlx4_0 hca_object=invalid hca_handle=2000
325";
326
327        const CONTENT_NG_INVALID_KEY: &str = "\
328mlx4_0 invalid=2
329";
330
331        const CONTENT_NG_MISSING_DATA: &str = "\
332mlx4_0 hca_object=2
333";
334
335        const CONTENT_NG_EXTRA_DATA: &str = "\
336mlx4_0 hca_object=2 hca_handle=2000 invalid
337";
338
339        for case in &[
340            CONTENT_NG_NOT_INT,
341            CONTENT_NG_INVALID_KEY,
342            CONTENT_NG_MISSING_DATA,
343            CONTENT_NG_EXTRA_DATA,
344        ] {
345            assert_eq!(
346                parse_limits(case.as_bytes()).unwrap_err().kind(),
347                ErrorKind::Parse
348            );
349        }
350
351        Ok(())
352    }
353}