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 = read_to_string(pth.join("boost"))
53            .ok()
54            .map(|boost| &boost == "1");
55
56        let mut policy = Vec::new();
57        for dir in read_dir(CPU_FREQ_DIR)? {
58            let dir = dir?;
59            let fname = dir.file_name();
60            if fname.to_string_lossy().contains("policy") {
61                policy.push(Policy::new(fname)?);
62            }
63        }
64
65        Ok(Self { policy, boost })
66    }
67}
68
69impl ToJson for CpuFreq {}
70
71#[derive(Debug, Deserialize, Serialize, Default, Clone)]
72pub struct Policy {
73    /// Maximum frequency from the BIOS
74    pub bios_limit: Option<u32>,
75
76    /// Core Performance Boost (only for AMD)
77    pub cpb: Option<bool>,
78
79    /// Maximum hardware frequency
80    pub cpu_max_freq: Option<u32>,
81
82    /// Minimum hardware frequency
83    pub cpu_min_freq: Option<u32>,
84
85    /// Time (nsecs) for transition between frequencies
86    pub cpuinfo_transition_latency: Option<bool>,
87
88    /// Available frequencies
89    pub scaling_available_frequencies: Option<Vec<u32>>,
90
91    /// Available frequency governors
92    pub scaling_available_governors: Option<Vec<String>>,
93
94    /// Current core frequency
95    pub scaling_cur_freq: Option<u32>,
96
97    /// Using cpufreq driver
98    pub scaling_driver: Option<String>,
99
100    /// Using governor
101    pub scaling_governor: Option<String>,
102
103    pub scaling_max_freq: Option<u32>,
104    pub scaling_min_freq: Option<u32>,
105    pub scaling_setspeed: Option<String>,
106}
107
108impl Policy {
109    fn get_data<T>(data: Option<String>) -> Option<T>
110    where
111        T: FromStr,
112    {
113        data.and_then(|d| d.trim().parse::<T>().ok())
114    }
115
116    pub fn new(policy: OsString) -> Result<Self> {
117        let dir = Path::new(CPU_FREQ_DIR);
118        let tgt = dir.join(policy);
119        if !dir.exists() || !tgt.exists() {
120            return Err(anyhow!("Directory {} does not exists!", dir.display()));
121        }
122
123        let read = |path: &PathBuf, name: &str| read_to_string(path.join(name));
124        let get_bool = |num: Option<u8>| num.map(|n| n != 0);
125
126        Ok(Self {
127            bios_limit: Self::get_data(read(&tgt, "bios_limit").ok()),
128            cpb: get_bool(Self::get_data(read(&tgt, "cpb").ok())),
129            cpu_max_freq: Self::get_data(read(&tgt, "cpuinfo_max_freq").ok()),
130            cpu_min_freq: Self::get_data(read(&tgt, "cpuinfo_min_freq").ok()),
131            cpuinfo_transition_latency: get_bool(
132                read(&tgt, "cpuinfo_transition_latency")
133                    .map(|d| d.trim().parse::<u8>().unwrap_or(0))
134                    .ok(),
135            ),
136            scaling_available_frequencies: read(&tgt, "scaling_available_frequencies")
137                .map(|d| {
138                    d.trim()
139                        .split_whitespace()
140                        .map(|freq| freq.parse::<u32>().ok())
141                        .filter(|freq| freq.is_some())
142                        .map(|freq| freq.unwrap())
143                        .collect::<Vec<_>>()
144                })
145                .ok(),
146            scaling_available_governors: read(&tgt, "scaling_available_governors")
147                .map(|d| {
148                    d.trim()
149                        .split_whitespace()
150                        .map(|gov| gov.to_string())
151                        .collect::<Vec<_>>()
152                })
153                .ok(),
154            scaling_cur_freq: Self::get_data(read(&tgt, "scaling_cur_freq").ok()),
155            scaling_driver: read(&tgt, "scaling_driver")
156                .ok()
157                .map(|s| s.trim().to_string()),
158            scaling_governor: read(&tgt, "scaling_governor")
159                .ok()
160                .map(|s| s.trim().to_string()),
161            scaling_max_freq: Self::get_data(read(&tgt, "scaling_max_freq").ok()),
162            scaling_min_freq: Self::get_data(read(&tgt, "scaling_min_freq").ok()),
163            scaling_setspeed: read(&tgt, "scaling_setspeed")
164                .ok()
165                .map(|s| s.trim().to_string()),
166        })
167    }
168}