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 #[cfg_attr(
38 feature = "regex",
39 builder(field(ty = "Vec<String>", build = "parse_include_regex(self.include_regex)?"))
40 )]
41 #[cfg(feature = "regex")]
42 include_regex: Vec<regex::Regex>,
43}
44
45struct CgroupsV2Iterator {
47 walker: walkdir::IntoIter,
48 include: Vec<glob::Pattern>,
49 #[cfg(feature = "regex")]
50 include_regex: Vec<regex::Regex>,
51 base_path: PathBuf,
52}
53
54struct CgroupsV1Iterator {
55 discovered: hash_set::IntoIter<PathBuf>,
56}
57
58impl Explorer {
59 #[must_use]
61 pub fn v1() -> ExplorerBuilder {
62 ExplorerBuilder::default().hierarchy(Box::new(V1::new()))
63 }
64
65 #[must_use]
67 pub fn v2() -> ExplorerBuilder {
68 ExplorerBuilder::default().hierarchy(Box::new(V2::new()))
69 }
70
71 #[must_use]
73 pub fn detect_version() -> ExplorerBuilder {
74 if is_cgroup2_unified_mode() {
75 ExplorerBuilder::default().hierarchy(Box::new(V2::new()))
76 } else {
77 ExplorerBuilder::default().hierarchy(Box::new(V1::new()))
78 }
79 }
80
81 #[must_use]
83 pub fn iter_cgroups(&self) -> Box<dyn Iterator<Item = Cgroup>> {
84 if self.hierarchy.v2() {
85 Box::new(self.iter_cgroups_v2())
86 } else {
87 Box::new(self.iter_cgroups_v1())
88 }
89 }
90
91 fn iter_cgroups_v2(&self) -> CgroupsV2Iterator {
92 let base_path = self.hierarchy.root();
93 let walker = WalkDir::new(base_path.clone())
94 .min_depth(1)
95 .sort_by_file_name()
96 .into_iter();
97 CgroupsV2Iterator {
98 walker,
99 include: self.include.clone(),
100 #[cfg(feature = "regex")]
101 include_regex: self.include_regex.clone(),
102 base_path,
103 }
104 }
105
106 fn iter_cgroups_v1(&self) -> CgroupsV1Iterator {
107 let hierarchy = V1::new();
108 let subystems = hierarchy.subsystems();
109 let base_path = hierarchy.root();
110
111 let mut matching_rel_paths = HashSet::new();
112 for subsystem in subystems {
113 let name = subsystem.controller_name();
114 let walker = WalkDir::new(base_path.join(&name))
115 .min_depth(1)
116 .sort_by_file_name()
117 .into_iter();
118 let base_controller_path = base_path.join(name);
119 for entry in walker {
120 let Ok(entry) = entry else { continue };
121 let path = entry.path();
122 if !entry.file_type().is_dir() {
123 continue;
124 }
125 let Ok(relative_path) = path.strip_prefix(&base_controller_path) else {
126 continue;
127 };
128 if relative_path.components().count() == 0 {
129 continue;
130 }
131 #[cfg(feature = "regex")]
132 let should_include = path_matches_include(&self.include, relative_path)
133 || path_matches_include_regex(&self.include_regex, relative_path);
134 #[cfg(not(feature = "regex"))]
135 let should_include = path_matches_include(&self.include, relative_path);
136
137 if should_include {
138 matching_rel_paths.insert(relative_path.to_path_buf());
139 }
140 }
141 }
142
143 CgroupsV1Iterator {
144 discovered: matching_rel_paths.into_iter(),
145 }
146 }
147}
148
149impl Iterator for CgroupsV2Iterator {
150 type Item = Cgroup;
151
152 fn next(&mut self) -> Option<Self::Item> {
153 loop {
154 let entry = self.walker.next();
155 match entry {
156 Some(Ok(entry)) => {
157 let path = entry.path();
158 if !entry.file_type().is_dir() {
159 continue;
160 }
161 let Ok(relative_path) = path.strip_prefix(&self.base_path) else {
162 continue;
163 };
164 if relative_path.components().count() == 0 {
165 continue;
166 }
167 if !path_matches_include(&self.include, relative_path) {
168 continue;
169 }
170 #[cfg(feature = "regex")]
171 if !path_matches_include_regex(&self.include_regex, relative_path) {
172 continue;
173 }
174 return Some(Cgroup::load(Box::new(V2::new()), relative_path));
175 }
176 Some(Err(_e)) => return None,
177 None => return None,
178 }
179 }
180 }
181}
182
183impl Iterator for CgroupsV1Iterator {
184 type Item = Cgroup;
185
186 fn next(&mut self) -> Option<Self::Item> {
187 self.discovered
188 .next()
189 .map(|path| Cgroup::load(Box::new(V1::new()), path))
190 }
191}
192
193fn path_matches_include(include: &[glob::Pattern], path: &Path) -> bool {
194 if include.is_empty() {
195 return true;
196 }
197 let path_str = path.to_string_lossy();
198 include.iter().any(|pattern| pattern.matches(&path_str))
199}
200
201#[cfg(feature = "regex")]
202fn path_matches_include_regex(include: &[regex::Regex], path: &Path) -> bool {
203 if include.is_empty() {
204 return true;
205 }
206 let path_str = path.to_string_lossy();
207 include.iter().any(|pattern| pattern.is_match(&path_str))
208}
209
210fn parse_include(include: Vec<String>) -> Result<Vec<glob::Pattern>, ExplorerBuilderError> {
211 if include.is_empty() {
212 Ok(Vec::new())
213 } else {
214 include
215 .into_iter()
216 .map(|include| {
217 glob::Pattern::new(&include)
218 .map_err(|e| ExplorerBuilderError::ValidationError(e.to_string()))
219 })
220 .collect()
221 }
222}
223
224#[cfg(feature = "regex")]
225fn parse_include_regex(include: Vec<String>) -> Result<Vec<regex::Regex>, ExplorerBuilderError> {
226 if include.is_empty() {
227 Ok(Vec::new())
228 } else {
229 include
230 .into_iter()
231 .map(|include| {
232 regex::Regex::new(&include)
233 .map_err(|e| ExplorerBuilderError::ValidationError(e.to_string()))
234 })
235 .collect()
236 }
237}