liboj_cgroups/subsystem/
cpuacct.rs

1use 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        // The first line is "cpu user system"
168        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}