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//! 
23//! ## Example
24//! ```no-test
25//! use ferrix_lib::cpu_freq::CpuFreq;
26//! 
27//! let freqs = CpuFreq::new().unwrap();
28//! dbg!(&freqs.policy);
29//! ```
30
31use anyhow::{Result, anyhow};
32use serde::{Deserialize, Serialize};
33use std::{
34    ffi::OsString,
35    fs::{read_dir, read_to_string},
36    path::{Path, PathBuf},
37    str::FromStr,
38};
39
40use crate::traits::ToJson;
41
42/// Information about power management and frequencies
43/// for the processors installed in the PC
44#[derive(Debug, Deserialize, Serialize, Clone)]
45pub struct CpuFreq {
46    /// Information on power and frequency management
47    /// for each processor core/thread
48    pub policy: Vec<Policy>,
49
50    /// Does the processor support Turbo Boost technology?
51    pub boost: Option<bool>,
52}
53
54const CPU_FREQ_DIR: &str = "/sys/devices/system/cpu/cpufreq/";
55
56impl CpuFreq {
57    pub fn new() -> Result<Self> {
58        let pth = Path::new(CPU_FREQ_DIR);
59        if !pth.exists() {
60            return Err(anyhow!(
61                "The directory '{CPU_FREQ_DIR}' was not found. Is \
62                 your system able to manage CPU frequencies for sure?",
63            ));
64        }
65
66        let boost = read_to_string(pth.join("boost"))
67            .ok()
68            .map(|boost| &boost == "1");
69
70        let mut policy = Vec::new();
71        for dir in read_dir(CPU_FREQ_DIR)? {
72            let dir = dir?;
73            let fname = dir.file_name();
74            if fname.to_string_lossy().contains("policy") {
75                policy.push(Policy::new(fname)?);
76            }
77        }
78
79        Ok(Self { policy, boost })
80    }
81}
82
83impl ToJson for CpuFreq {}
84
85/// Information about power management and the frequency
86/// of a specific core/thread
87#[derive(Debug, Deserialize, Serialize, Default, Clone)]
88pub struct Policy {
89    /// Maximum frequency from the BIOS
90    pub bios_limit: Option<u32>,
91
92    /// Core Performance Boost (only for AMD)
93    pub cpb: Option<bool>,
94
95    /// Maximum hardware frequency
96    pub cpu_max_freq: Option<u32>,
97
98    /// Minimum hardware frequency
99    pub cpu_min_freq: Option<u32>,
100
101    /// Time (nsecs) for transition between frequencies
102    pub cpuinfo_transition_latency: Option<bool>,
103
104    /// Available frequencies
105    pub scaling_available_frequencies: Option<Vec<u32>>,
106
107    /// Available frequency governors
108    pub scaling_available_governors: Option<Vec<String>>,
109
110    /// Current core frequency
111    pub scaling_cur_freq: Option<u32>,
112
113    /// Using cpufreq driver
114    pub scaling_driver: Option<String>,
115
116    /// Using governor
117    pub scaling_governor: Option<String>,
118
119    pub scaling_max_freq: Option<u32>,
120    pub scaling_min_freq: Option<u32>,
121    pub scaling_setspeed: Option<String>,
122}
123
124impl Policy {
125    fn get_data<T>(data: Option<String>) -> Option<T>
126    where
127        T: FromStr,
128    {
129        data.and_then(|d| d.trim().parse::<T>().ok())
130    }
131
132    pub fn new(policy: OsString) -> Result<Self> {
133        let dir = Path::new(CPU_FREQ_DIR);
134        let tgt = dir.join(policy);
135        if !dir.exists() || !tgt.exists() {
136            return Err(anyhow!("Directory {} does not exists!", dir.display()));
137        }
138
139        let read = |path: &PathBuf, name: &str| read_to_string(path.join(name));
140        let get_bool = |num: Option<u8>| num.map(|n| n != 0);
141
142        Ok(Self {
143            bios_limit: Self::get_data(read(&tgt, "bios_limit").ok()),
144            cpb: get_bool(Self::get_data(read(&tgt, "cpb").ok())),
145            cpu_max_freq: Self::get_data(read(&tgt, "cpuinfo_max_freq").ok()),
146            cpu_min_freq: Self::get_data(read(&tgt, "cpuinfo_min_freq").ok()),
147            cpuinfo_transition_latency: get_bool(
148                read(&tgt, "cpuinfo_transition_latency")
149                    .map(|d| d.trim().parse::<u8>().unwrap_or(0))
150                    .ok(),
151            ),
152            scaling_available_frequencies: read(&tgt, "scaling_available_frequencies")
153                .map(|d| {
154                    d.trim()
155                        .split_whitespace()
156                        .map(|freq| freq.parse::<u32>().ok())
157                        .filter(|freq| freq.is_some())
158                        .map(|freq| freq.unwrap())
159                        .collect::<Vec<_>>()
160                })
161                .ok(),
162            scaling_available_governors: read(&tgt, "scaling_available_governors")
163                .map(|d| {
164                    d.trim()
165                        .split_whitespace()
166                        .map(|gov| gov.to_string())
167                        .collect::<Vec<_>>()
168                })
169                .ok(),
170            scaling_cur_freq: Self::get_data(read(&tgt, "scaling_cur_freq").ok()),
171            scaling_driver: read(&tgt, "scaling_driver")
172                .ok()
173                .map(|s| s.trim().to_string()),
174            scaling_governor: read(&tgt, "scaling_governor")
175                .ok()
176                .map(|s| s.trim().to_string()),
177            scaling_max_freq: Self::get_data(read(&tgt, "scaling_max_freq").ok()),
178            scaling_min_freq: Self::get_data(read(&tgt, "scaling_min_freq").ok()),
179            scaling_setspeed: read(&tgt, "scaling_setspeed")
180                .ok()
181                .map(|s| s.trim().to_string()),
182        })
183    }
184}