1use anyhow::{Result, anyhow};
24use serde::{Deserialize, Serialize};
25use std::{
26 ffi::OsString,
27 fs::{read_dir, read_to_string},
28 path::{Path, PathBuf},
29 str::FromStr,
30};
31
32use crate::traits::ToJson;
33
34#[derive(Debug, Deserialize, Serialize, Clone)]
35pub struct CpuFreq {
36 pub policy: Vec<Policy>,
37 pub boost: Option<bool>,
38}
39
40const CPU_FREQ_DIR: &str = "/sys/devices/system/cpu/cpufreq/";
41
42impl CpuFreq {
43 pub fn new() -> Result<Self> {
44 let pth = Path::new(CPU_FREQ_DIR);
45 if !pth.exists() {
46 return Err(anyhow!(
47 "The directory '{CPU_FREQ_DIR}' was not found. Is \
48 your system able to manage CPU frequencies for sure?",
49 ));
50 }
51
52 let boost = match read_to_string(pth.join("boost")).ok() {
53 Some(boost) => Some(&boost == "1"),
54 None => None,
55 };
56
57 let mut policy = Vec::new();
58 for dir in read_dir(CPU_FREQ_DIR)? {
59 let dir = dir?;
60 let fname = dir.file_name();
61 if fname.to_string_lossy().contains("policy") {
62 policy.push(Policy::new(fname)?);
63 }
64 }
65
66 Ok(Self { policy, boost })
67 }
68}
69
70impl ToJson for CpuFreq {}
71
72#[derive(Debug, Deserialize, Serialize, Default, Clone)]
73pub struct Policy {
74 pub bios_limit: Option<u32>,
76
77 pub cpb: Option<bool>,
79
80 pub cpu_max_freq: Option<u32>,
82
83 pub cpu_min_freq: Option<u32>,
85
86 pub cpuinfo_transition_latency: Option<bool>,
88
89 pub scaling_available_frequencies: Option<Vec<u32>>,
91
92 pub scaling_available_governors: Option<Vec<String>>,
94
95 pub scaling_cur_freq: Option<u32>,
97
98 pub scaling_driver: Option<String>,
100
101 pub scaling_governor: Option<String>,
103
104 pub scaling_max_freq: Option<u32>,
105 pub scaling_min_freq: Option<u32>,
106 pub scaling_setspeed: Option<String>,
107}
108
109impl Policy {
110 fn get_data<T>(data: Option<String>) -> Option<T>
111 where
112 T: FromStr,
113 {
114 data.and_then(|d| d.trim().parse::<T>().ok())
115 }
116
117 pub fn new(policy: OsString) -> Result<Self> {
118 let dir = Path::new(CPU_FREQ_DIR);
119 let tgt = dir.join(policy);
120 if !dir.exists() || !tgt.exists() {
121 return Err(anyhow!("Directory {} does not exists!", dir.display()));
122 }
123
124 let read = |path: &PathBuf, name: &str| read_to_string(path.join(name));
125 let get_bool = |num: Option<u8>| match num {
126 Some(num) => Some(if num != 0 { true } else { false }),
127 None => None,
128 };
129
130 Ok(Self {
131 bios_limit: Self::get_data(read(&tgt, "bios_limit").ok()),
132 cpb: get_bool(Self::get_data(read(&tgt, "cpb").ok())),
133 cpu_max_freq: Self::get_data(read(&tgt, "cpuinfo_max_freq").ok()),
134 cpu_min_freq: Self::get_data(read(&tgt, "cpuinfo_min_freq").ok()),
135 cpuinfo_transition_latency: get_bool(
136 read(&tgt, "cpuinfo_transition_latency")
137 .and_then(|d| Ok(d.trim().parse::<u8>().unwrap_or(0)))
138 .ok(),
139 ),
140 scaling_available_frequencies: read(&tgt, "scaling_available_frequencies")
141 .and_then(|d| {
142 Ok(d.trim()
143 .split_whitespace()
144 .map(|freq| freq.parse::<u32>().ok())
145 .filter(|freq| freq.is_some())
146 .map(|freq| freq.unwrap())
147 .collect::<Vec<_>>())
148 })
149 .ok(),
150 scaling_available_governors: read(&tgt, "scaling_available_governors")
151 .and_then(|d| {
152 Ok(d.trim()
153 .split_whitespace()
154 .map(|gov| gov.to_string())
155 .collect::<Vec<_>>())
156 })
157 .ok(),
158 scaling_cur_freq: Self::get_data(read(&tgt, "scaling_cur_freq").ok()),
159 scaling_driver: read(&tgt, "scaling_driver")
160 .ok()
161 .and_then(|s| Some(s.trim().to_string())),
162 scaling_governor: read(&tgt, "scaling_governor")
163 .ok()
164 .and_then(|s| Some(s.trim().to_string())),
165 scaling_max_freq: Self::get_data(read(&tgt, "scaling_max_freq").ok()),
166 scaling_min_freq: Self::get_data(read(&tgt, "scaling_min_freq").ok()),
167 scaling_setspeed: read(&tgt, "scaling_setspeed")
168 .ok()
169 .and_then(|s| Some(s.trim().to_string())),
170 })
171 }
172}