Skip to main content

ferrix_lib/
cpu_freq.rs

1/* cpu_freq.rs
2 *
3 * Copyright 2025-2026 Michail Krasnov <mskrasnov07@ya.ru>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21//! Get information about CPU frequency
22
23use 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    /// Maximum frequency from the BIOS
75    pub bios_limit: Option<u32>,
76
77    /// Core Performance Boost (only for AMD)
78    pub cpb: Option<bool>,
79
80    /// Maximum hardware frequency
81    pub cpu_max_freq: Option<u32>,
82
83    /// Minimum hardware frequency
84    pub cpu_min_freq: Option<u32>,
85
86    /// Time (nsecs) for transition between frequencies
87    pub cpuinfo_transition_latency: Option<bool>,
88
89    /// Available frequencies
90    pub scaling_available_frequencies: Option<Vec<u32>>,
91
92    /// Available frequency governors
93    pub scaling_available_governors: Option<Vec<String>>,
94
95    /// Current core frequency
96    pub scaling_cur_freq: Option<u32>,
97
98    /// Using cpufreq driver
99    pub scaling_driver: Option<String>,
100
101    /// Using governor
102    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}