mediocore/
lib.rs

1#[macro_use]
2extern crate log;
3extern crate serde;
4#[macro_use]
5extern crate serde_derive;
6
7use std::fs;
8use std::io;
9use std::io::{Error, ErrorKind, Write};
10use std::path::PathBuf;
11
12macro_rules! parse_num {
13    ($g:ident, $op:expr) => {{
14        let mut chars = fs::read_to_string($g.join($op))?;
15        chars.retain(|c| c.is_digit(10));
16
17        match chars.parse() {
18            Ok(x) => x,
19            Err(e) => {
20                return Err(io::Error::new(
21                    io::ErrorKind::InvalidData,
22                    format!("Cause{}", e),
23                ));
24            }
25        }
26    }};
27}
28
29/// find relevant sysfs folders in /sys/devices/system/cpu/cpu<x>
30pub fn discover_core_settings() -> io::Result<Vec<Core>> {
31    let cpu_root = fs::read_dir("/sys/devices/system/cpu/")?;
32    debug!("Content of /sys/devices/system/cpu/  {:#?}", cpu_root);
33
34    let is_core = |p: &fs::DirEntry| {
35        let f = p
36            .file_name()
37            .into_string()
38            .expect("Encountered invalid path while discovering core directories");
39        f.contains("cpu") && !(f.contains("cpuidle") || f.contains("cpufreq"))
40    };
41
42    cpu_root
43        // WARN: error cases described by read dir seem unrealistic at first so we're gonna ignore them
44        .filter_map(|e| e.ok())
45        .filter(|p| is_core(p))
46        .map(|p| p.path())
47        .inspect(|c| debug!("Found core: {:?}", c))
48        .try_fold(Vec::new(), |mut cores, c| {
49            let c = Core::discover(c)?;
50            cores.push(c);
51            Ok(cores)
52        })
53}
54
55/// Representation of the current cpufrequency serttings of a single core
56/// Can be obtained by running [discover_core_settings]
57#[derive(Clone, Debug, Serialize, Deserialize)]
58pub struct Core {
59    /// Path to the core directory
60    core: PathBuf,
61    /// Number of the core
62    num: u32,
63    /// CPU Minimum Frequency
64    cpuinfo_max_freq: u32,
65    /// CPU Maximum Frequency
66    cpuinfo_min_freq: u32,
67    /// List of possible values for the governor
68    scaling_available_governors: Vec<String>,
69    /// Current upper frequency limit - the governor may increase frequency up to this value
70    scaling_max_freq: u32,
71    /// Current lower frequency limit - the governor may reduce the frequency down to this value
72    scaling_min_freq: u32,
73    /// Currently set scaling governor
74    scaling_governor: String,
75}
76
77impl Core {
78    /// discover settings for the core specified by its path
79    pub fn discover(core: PathBuf) -> io::Result<Core> {
80        let g = core.join("cpufreq");
81
82        let cpuinfo_min_freq: u32 = parse_num!(g, "cpuinfo_min_freq");
83        let cpuinfo_max_freq: u32 = parse_num!(g, "cpuinfo_max_freq");
84        let scaling_min_freq: u32 = parse_num!(g, "scaling_min_freq");
85        let scaling_max_freq: u32 = parse_num!(g, "scaling_max_freq");
86        let scaling_governor = {
87            let mut chars = fs::read_to_string(g.join("scaling_governor"))?;
88            chars.retain(|c| !c.is_control());
89            chars
90        };
91
92        let scaling_available_governors = {
93            let mut chars = fs::read_to_string(g.join("scaling_available_governors"))?;
94            chars.retain(|c| !c.is_control());
95            chars.split(&" ").map(|s| s.into()).collect()
96        };
97
98        // parse the number
99        let num = core
100            .to_str()
101            .expect("Failed to convert PathBuf to String")
102            .rsplit("cpu")
103            .next()
104            .expect("Core name did not contain 'cpu'")
105            .parse()
106            .expect("Failed to parse the u32 core number");
107
108        let c = Core {
109            core,
110            num,
111            cpuinfo_max_freq,
112            cpuinfo_min_freq,
113            scaling_available_governors,
114            scaling_governor,
115            scaling_min_freq,
116            scaling_max_freq,
117        };
118        debug!("Read settings : {:#?}", c);
119
120        Ok(c)
121    }
122
123    /// returns the number of the core
124    pub fn num(&self) -> u32 {
125        self.num
126    }
127
128    /// returns cpu minimum frequency in kHz
129    pub fn cpu_min(&self) -> u32 {
130        self.cpuinfo_min_freq
131    }
132
133    /// returns cpu maximum frequency in kHz
134    pub fn cpu_max(&self) -> u32 {
135        self.cpuinfo_max_freq
136    }
137
138    /// returns current min scaling frequency in kHz
139    pub fn curr_min(&self) -> u32 {
140        self.scaling_min_freq
141    }
142
143    /// returns current max scaling frequency in kHz
144    pub fn curr_max(&self) -> u32 {
145        self.scaling_max_freq
146    }
147
148    /// returns the current governor
149    pub fn curr_gov(&self) -> &str {
150        self.scaling_governor.as_ref()
151    }
152
153    /// returns available governors
154    pub fn available_govs(&self) -> &[String] {
155        self.scaling_available_governors.as_ref()
156    }
157
158    /// Validate the given minimum value. Must be >= the discovered CPU frequency minimum
159    pub fn validate_min(&self, freq: u32) -> io::Result<u32> {
160        if self.cpuinfo_min_freq <= freq && freq <= self.scaling_max_freq {
161            Ok(freq)
162        } else {
163            Err(Error::new(
164                ErrorKind::InvalidInput,
165                format!(
166                    "Min Frequency {} not in ({},{}]",
167                    freq, self.cpuinfo_min_freq, self.scaling_max_freq
168                ),
169            ))
170        }
171    }
172
173    /// Validate the given maximum value. Must be >= current min and <= CPU frequency maximum
174    pub fn validate_max(&self, freq: u32) -> io::Result<u32> {
175        if (self.cpuinfo_min_freq <= freq && self.scaling_min_freq <= freq)
176            && freq <= self.cpuinfo_max_freq
177        {
178            Ok(freq)
179        } else {
180            Err(Error::new(
181                ErrorKind::InvalidInput,
182                format!(
183                    "Max Frequency {} not in min({},{})..={}",
184                    freq, self.cpuinfo_min_freq, self.scaling_min_freq, self.cpuinfo_max_freq
185                ),
186            ))
187        }
188    }
189
190    /// Validate the governor by checking against the list of available governors
191    pub fn validate_governor<'a>(&self, governor: &'a str) -> io::Result<&'a str> {
192        if self
193            .scaling_available_governors
194            .iter()
195            .any(|g| g.as_str().eq(governor))
196        {
197            Ok(governor)
198        } else {
199            Err(Error::new(
200                ErrorKind::InvalidInput,
201                format!(
202                    "Governor {} not available. Must be one of {:?}",
203                    governor, self.scaling_available_governors
204                ),
205            ))
206        }
207    }
208
209    /// Set the minimum scaling frequency (lower frequency limit)
210    /// This operation is not checked by mediocore, but the kernel may refuse to accept certain inputs.  
211    /// Use [Core::validate_min] on the value beforehand.
212    pub fn set_min(&mut self, freq: u32) -> io::Result<()> {
213        debug!("Setting minimum scaling frequency {} on {}", freq, self.num);
214        let mut f = fs::OpenOptions::new()
215            .write(true)
216            .open(self.core.join("cpufreq/scaling_min_freq"))?;
217        f.write_all(format!("{}", freq).as_ref())?;
218        Ok(())
219    }
220
221    /// Set the maximum scaling frequency (lower frequency limit)  
222    /// This operation is not checked by mediocore, but the kernel may refuse to accept certain inputs.  
223    /// Use [Core::validate_max] on the value beforehand.
224    pub fn set_max(&mut self, freq: u32) -> io::Result<()> {
225        debug!("Setting maximum scaling frequency {} on {}", freq, self.num);
226        let mut f = fs::OpenOptions::new()
227            .write(true)
228            .open(self.core.join("cpufreq/scaling_max_freq"))?;
229        f.write_all(format!("{}", freq).as_ref())?;
230        Ok(())
231    }
232
233    /// Apply the given governor
234    /// This operation is not checked by mediocore, but the kernel may refuse to accept certain inputs.
235    /// Use [Core::validate_governor] on the value beforehand.
236    pub fn set_governor(&mut self, guvnor: &str) -> io::Result<()> {
237        debug!("Setting governor {} on {}", guvnor, self.num);
238        fs::OpenOptions::new()
239            .write(true)
240            .read(false)
241            .open(self.core.join("cpufreq/scaling_governor"))
242            .and_then(|mut f| f.write_all(guvnor.as_bytes()))
243            .map(|_| ())
244    }
245}
246
247#[cfg(test)]
248mod test {
249    use io::{ErrorKind, Result};
250    use std::path::PathBuf;
251    use Core;
252
253    #[test]
254    fn freq_validation() {
255        let s = Core {
256            core: PathBuf::from("/sys/devices/system/cpu/cpu0"),
257            num: 0,
258            cpuinfo_min_freq: 800000,
259            cpuinfo_max_freq: 2500000,
260            scaling_available_governors: vec![],
261            scaling_governor: "".into(),
262            scaling_min_freq: 850000,
263            scaling_max_freq: 900000,
264        };
265
266        let check_val = |x, v| match x {
267            Ok(u) => v == u,
268            Err(_) => false,
269        };
270
271        let check_err = |x: Result<u32>| match x {
272            Ok(_) => false,
273            Err(ref e) if e.kind() == ErrorKind::InvalidInput => true,
274            _ => false,
275        };
276
277        assert!(check_val(s.validate_min(800000), 800000));
278        assert!(check_val(s.validate_min(850000), 850000));
279        assert!(check_err(s.validate_min(1000000)));
280
281        assert!(check_val(s.validate_max(1000000), 1000000));
282        assert!(check_err(s.validate_max(8000000)));
283    }
284
285    #[test]
286    fn govnor_validation() {
287        let s = Core {
288            core: PathBuf::from(&"/sys/devices/system/cpu/cpu0"),
289            num: 0,
290            cpuinfo_min_freq: 800000,
291            cpuinfo_max_freq: 2500000,
292            scaling_available_governors: vec!["performance".into(), "powersave".into()],
293            scaling_governor: "powersave".into(),
294            scaling_min_freq: 850000,
295            scaling_max_freq: 900000,
296        };
297
298        assert!(s.validate_governor(&"performance").is_ok());
299        assert!(s.validate_governor(&"conservative").is_err());
300    }
301
302}