liboj_cgroups/subsystem/
cpuacct.rs1use std::fs;
2use std::io;
3use std::ops::Deref;
4use std::path::Path;
5use std::str::FromStr;
6use std::time::Duration;
7
8use lazy_static::lazy_static;
9use nix::unistd;
10
11use super::Controller;
12use crate::{
13 attr_file::{ReadAttr, ResetAttr, StatMap},
14 hierarchy::HierarchyNode,
15};
16
17lazy_static! {
18 static ref USER_HZ: usize = {
19 match unistd::sysconf(unistd::SysconfVar::CLK_TCK) {
20 Ok(Some(user_hz)) => user_hz as usize,
21 Ok(None) => panic!("Failed to get USER_HZ"),
22 Err(e) => panic!("Failed to get USER_HZ. {}", e),
23 }
24 };
25}
26
27pub trait CpuAcctController: Controller {
28 fn reset(&self) -> Box<dyn '_ + ResetAttr>;
29 fn stat(&self) -> Box<dyn '_ + ReadAttr<Stat>>;
30 fn usage(&self) -> Box<dyn '_ + ReadAttr<Duration>>;
31 fn usage_all(&self) -> Box<dyn '_ + ReadAttr<UsageAll>>;
32 fn usage_user(&self) -> Box<dyn '_ + ReadAttr<Duration>>;
33 fn usage_sys(&self) -> Box<dyn '_ + ReadAttr<Duration>>;
34 fn usage_percpu(&self) -> Box<dyn '_ + ReadAttr<Vec<Duration>>>;
35 fn usage_percpu_user(&self) -> Box<dyn '_ + ReadAttr<Vec<Duration>>>;
36 fn usage_percpu_sys(&self) -> Box<dyn '_ + ReadAttr<Vec<Duration>>>;
37}
38
39impl CpuAcctController for HierarchyNode {
40 fn reset(&self) -> Box<dyn '_ + ResetAttr> {
41 let file = self.as_path().join("cpuacct.usage");
42 Box::new(UsageFile(file))
43 }
44
45 fn stat(&self) -> Box<dyn '_ + ReadAttr<Stat>> {
46 let file = self.as_path().join("cpuacct.stat");
47 Box::new(StatFile(file))
48 }
49
50 fn usage(&self) -> Box<dyn '_ + ReadAttr<Duration>> {
51 let file = self.as_path().join("cpuacct.usage");
52 Box::new(UsageFile(file))
53 }
54
55 fn usage_all(&self) -> Box<dyn '_ + ReadAttr<UsageAll>> {
56 let file = self.as_path().join("cpuacct.usage_all");
57 Box::new(UsageAllFile(file))
58 }
59
60 fn usage_user(&self) -> Box<dyn '_ + ReadAttr<Duration>> {
61 let file = self.as_path().join("cpuacct.usage_user");
62 Box::new(UsageFile(file))
63 }
64
65 fn usage_sys(&self) -> Box<dyn '_ + ReadAttr<Duration>> {
66 let file = self.as_path().join("cpuacct.usage_sys");
67 Box::new(UsageFile(file))
68 }
69
70 fn usage_percpu(&self) -> Box<dyn '_ + ReadAttr<Vec<Duration>>> {
71 let file = self.as_path().join("cpuacct.usage_percpu");
72 Box::new(PerCpuFile(file))
73 }
74
75 fn usage_percpu_user(&self) -> Box<dyn '_ + ReadAttr<Vec<Duration>>> {
76 let file = self.as_path().join("cpuacct.usage_percpu_user");
77 Box::new(PerCpuFile(file))
78 }
79
80 fn usage_percpu_sys(&self) -> Box<dyn '_ + ReadAttr<Vec<Duration>>> {
81 let file = self.as_path().join("cpuacct.usage_percpu_sys");
82 Box::new(PerCpuFile(file))
83 }
84}
85
86struct StatFile<P: AsRef<Path>>(P);
87
88impl<P: AsRef<Path>> ReadAttr<Stat> for StatFile<P> {
89 fn read(&self) -> io::Result<Stat> {
90 let s = fs::read_to_string(&self.0)?;
91 let stat_map = StatMap::from(s.as_str());
92 let mut res = Stat {
93 user: Duration::from_secs(0),
94 system: Duration::from_secs(0),
95 };
96 match stat_map.get("user").map(|s| s.parse::<f32>()) {
97 Some(Ok(usage)) => res.user = Duration::from_secs_f32(usage / *USER_HZ as f32),
98 Some(Err(_)) | None => {
99 return Err(io::Error::new(
100 io::ErrorKind::InvalidData,
101 "failed to parse user cpu usage in cpuacct.stat",
102 ));
103 }
104 }
105 match stat_map.get("system").map(|s| s.parse::<f32>()) {
106 Some(Ok(usage)) => res.system = Duration::from_secs_f32(usage / *USER_HZ as f32),
107 Some(Err(_)) | None => {
108 return Err(io::Error::new(
109 io::ErrorKind::InvalidData,
110 "failed to parse system cpu usage in cpuacct.stat",
111 ))
112 }
113 }
114 Ok(res)
115 }
116}
117
118pub struct Stat {
119 pub user: Duration,
120 pub system: Duration,
121}
122
123struct UsageFile<P: AsRef<Path>>(P);
124
125impl<P: AsRef<Path>> ReadAttr<Duration> for UsageFile<P> {
126 fn read(&self) -> io::Result<Duration> {
127 match fs::read_to_string(&self.0)?.trim().parse() {
128 Ok(usage) => Ok(Duration::from_nanos(usage)),
129 Err(_) => Err(io::Error::new(
130 io::ErrorKind::InvalidData,
131 "failed to parse cpu usage in cgroup",
132 )),
133 }
134 }
135}
136
137impl<P: AsRef<Path>> ResetAttr for UsageFile<P> {
138 fn reset(&self) -> io::Result<()> {
139 fs::write(&self.0, b"0")
140 }
141}
142
143struct UsageAllFile<P: AsRef<Path>>(P);
144
145impl<P: AsRef<Path>> ReadAttr<UsageAll> for UsageAllFile<P> {
146 fn read(&self) -> io::Result<UsageAll> {
147 let file = self.0.as_ref();
148 match fs::read_to_string(file)?.parse() {
149 Ok(usages) => Ok(usages),
150 Err(_) => Err(io::Error::new(
151 io::ErrorKind::InvalidData,
152 format!("failed to parse cpu usage from {}", file.display()),
153 )),
154 }
155 }
156}
157
158pub struct UsageAll {
159 usages: Vec<Usage>,
160}
161
162impl FromStr for UsageAll {
163 type Err = io::Error;
164
165 fn from_str(s: &str) -> io::Result<UsageAll> {
166 let mut usages = Vec::new();
167 for line in s.lines().skip(1) {
169 match line.parse() {
170 Ok(usage) => usages.push(usage),
171 Err(e) => return Err(e),
172 }
173 }
174 Ok(UsageAll { usages })
175 }
176}
177
178impl Deref for UsageAll {
179 type Target = Vec<Usage>;
180
181 fn deref(&self) -> &Vec<Usage> {
182 &self.usages
183 }
184}
185
186pub struct Usage {
187 pub cpu: usize,
188 pub user: Duration,
189 pub system: Duration,
190}
191
192impl FromStr for Usage {
193 type Err = io::Error;
194
195 fn from_str(s: &str) -> io::Result<Usage> {
196 let mut values = s.split_whitespace();
197 let cpu: usize = match values.next().map(|value| value.parse()) {
198 Some(Ok(value)) => value,
199 Some(Err(_)) => {
200 return Err(io::Error::new(
201 io::ErrorKind::InvalidData,
202 "failed to parse cpu index when parsing cpu usage.",
203 ));
204 }
205 None => {
206 return Err(io::Error::new(
207 io::ErrorKind::InvalidData,
208 "failed to parse cpu usage because cpu index is missing.",
209 ));
210 }
211 };
212 let user: Duration = match values.next().map(|value| value.parse()) {
213 Some(Ok(value)) => Duration::from_nanos(value),
214 Some(Err(_)) => {
215 return Err(io::Error::new(
216 io::ErrorKind::InvalidData,
217 "failed to parse user cpu usage when parsing cpu usage.",
218 ));
219 }
220 None => {
221 return Err(io::Error::new(
222 io::ErrorKind::InvalidData,
223 "failed to parse cpu usage because user cpu usage is missing.",
224 ));
225 }
226 };
227 let system: Duration = match values.next().map(|value| value.parse()) {
228 Some(Ok(value)) => Duration::from_nanos(value),
229 Some(Err(_)) => {
230 return Err(io::Error::new(
231 io::ErrorKind::InvalidData,
232 "failed to parse system cpu usage when parsing cpu usage.",
233 ));
234 }
235 None => {
236 return Err(io::Error::new(
237 io::ErrorKind::InvalidData,
238 "failed to parse cpu usage because system cpu usage is missing.",
239 ));
240 }
241 };
242 Ok(Usage { cpu, user, system })
243 }
244}
245
246struct PerCpuFile<P: AsRef<Path>>(P);
247
248impl<P: AsRef<Path>> ReadAttr<Vec<Duration>> for PerCpuFile<P> {
249 fn read(&self) -> io::Result<Vec<Duration>> {
250 let file = self.0.as_ref();
251 let mut usages = Vec::new();
252 for usage in fs::read_to_string(&file)?.split_whitespace() {
253 match usage.parse() {
254 Ok(usage) => usages.push(Duration::from_nanos(usage)),
255 Err(_) => {
256 return Err(io::Error::new(
257 io::ErrorKind::InvalidData,
258 format!("{} does not contains a valid cpu usage", file.display()),
259 ))
260 }
261 }
262 }
263 Ok(usages)
264 }
265}