cgroups_explorer/
explorer.rs1use std::{
2 collections::{HashSet, hash_set},
3 path::{Path, PathBuf},
4};
5
6use cgroups_rs::{
7 Cgroup, Hierarchy,
8 hierarchies::{V1, V2, is_cgroup2_unified_mode},
9};
10use derive_builder::Builder;
11use walkdir::WalkDir;
12
13#[derive(Builder)]
28#[builder(pattern = "owned")]
29pub struct Explorer {
30 hierarchy: Box<dyn Hierarchy>,
32
33 #[builder(field(ty = "Vec<String>", build = "parse_include(self.include)?"))]
35 include: Vec<glob::Pattern>,
36}
37
38struct CgroupsV2Iterator {
40 walker: walkdir::IntoIter,
41 include: Vec<glob::Pattern>,
42 base_path: PathBuf,
43}
44
45struct CgroupsV1Iterator {
46 discovered: hash_set::IntoIter<PathBuf>,
47}
48
49impl Explorer {
50 #[must_use]
52 pub fn v1() -> ExplorerBuilder {
53 ExplorerBuilder::default().hierarchy(Box::new(V1::new()))
54 }
55
56 #[must_use]
58 pub fn v2() -> ExplorerBuilder {
59 ExplorerBuilder::default().hierarchy(Box::new(V2::new()))
60 }
61
62 #[must_use]
64 pub fn detect_version() -> ExplorerBuilder {
65 if is_cgroup2_unified_mode() {
66 ExplorerBuilder::default().hierarchy(Box::new(V2::new()))
67 } else {
68 ExplorerBuilder::default().hierarchy(Box::new(V1::new()))
69 }
70 }
71
72 #[must_use]
74 pub fn iter_cgroups(&self) -> Box<dyn Iterator<Item = Cgroup>> {
75 if self.hierarchy.v2() {
76 Box::new(self.iter_cgroups_v2())
77 } else {
78 Box::new(self.iter_cgroups_v1())
79 }
80 }
81
82 fn iter_cgroups_v2(&self) -> CgroupsV2Iterator {
83 let base_path = self.hierarchy.root();
84 let walker = WalkDir::new(base_path.clone())
85 .min_depth(1)
86 .sort_by_file_name()
87 .into_iter();
88 CgroupsV2Iterator {
89 walker,
90 include: self.include.clone(),
91 base_path,
92 }
93 }
94
95 fn iter_cgroups_v1(&self) -> CgroupsV1Iterator {
96 let hierarchy = V1::new();
97 let subystems = hierarchy.subsystems();
98 let base_path = hierarchy.root();
99
100 let mut matching_rel_paths = HashSet::new();
101 for subsystem in subystems {
102 let name = subsystem.controller_name();
103 let walker = WalkDir::new(base_path.join(&name))
104 .min_depth(1)
105 .sort_by_file_name()
106 .into_iter();
107 let base_controller_path = base_path.join(name);
108 for entry in walker {
109 let Ok(entry) = entry else { continue };
110 let path = entry.path();
111 if !entry.file_type().is_dir() {
112 continue;
113 }
114 let Ok(relative_path) = path.strip_prefix(&base_controller_path) else {
115 continue;
116 };
117 if relative_path.components().count() == 0 {
118 continue;
119 }
120 if self.include.is_empty() || path_matches_include(&self.include, relative_path) {
121 matching_rel_paths.insert(relative_path.to_path_buf());
122 }
123 }
124 }
125
126 CgroupsV1Iterator {
127 discovered: matching_rel_paths.into_iter(),
128 }
129 }
130}
131
132impl Iterator for CgroupsV2Iterator {
133 type Item = Cgroup;
134
135 fn next(&mut self) -> Option<Self::Item> {
136 loop {
137 let entry = self.walker.next();
138 match entry {
139 Some(Ok(entry)) => {
140 let path = entry.path();
141 if !entry.file_type().is_dir() {
142 continue;
143 }
144 let Ok(relative_path) = path.strip_prefix(&self.base_path) else {
145 continue;
146 };
147 if relative_path.components().count() == 0 {
148 continue;
149 }
150 if !path_matches_include(&self.include, relative_path) {
151 continue;
152 }
153 return Some(Cgroup::load(Box::new(V2::new()), relative_path));
154 }
155 Some(Err(_e)) => return None,
156 None => return None,
157 }
158 }
159 }
160}
161
162impl Iterator for CgroupsV1Iterator {
163 type Item = Cgroup;
164
165 fn next(&mut self) -> Option<Self::Item> {
166 self.discovered
167 .next()
168 .map(|path| Cgroup::load(Box::new(V1::new()), path))
169 }
170}
171
172fn path_matches_include(include: &[glob::Pattern], path: &Path) -> bool {
173 if include.is_empty() {
174 return true;
175 }
176 let path_str = path.to_string_lossy();
177 include.iter().any(|pattern| pattern.matches(&path_str))
178}
179
180fn parse_include(include: Vec<String>) -> Result<Vec<glob::Pattern>, ExplorerBuilderError> {
181 if include.is_empty() {
182 Ok(Vec::new())
183 } else {
184 include
185 .into_iter()
186 .map(|include| {
187 glob::Pattern::new(&include)
188 .map_err(|e| ExplorerBuilderError::ValidationError(e.to_string()))
189 })
190 .collect()
191 }
192}