ferrix_lib/
cpu_freq.rs

1/* cpu_freq.rs
2 *
3 * Copyright 2025 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};
30
31use crate::traits::ToJson;
32
33#[derive(Debug, Deserialize, Serialize, Clone)]
34pub struct CpuFreq {
35    pub policy: Vec<Policy>,
36    pub boost: Option<bool>,
37}
38
39const CPU_FREQ_DIR: &str = "/sys/devices/system/cpu/cpufreq/";
40
41impl CpuFreq {
42    pub fn new() -> Result<Self> {
43        let pth = Path::new(CPU_FREQ_DIR);
44        let boost = match read_to_string(pth.join("boost")).ok() {
45            Some(boost) => Some(&boost == "1"),
46            None => None,
47        };
48
49        let mut policy = Vec::new();
50        for dir in read_dir(CPU_FREQ_DIR)? {
51            let dir = dir?;
52            let fname = dir.file_name();
53            if fname.to_string_lossy().contains("policy") {
54                policy.push(Policy::new(fname)?);
55            }
56        }
57
58        Ok(Self { policy, boost })
59    }
60}
61
62impl ToJson for CpuFreq {}
63
64#[derive(Debug, Deserialize, Serialize, Default, Clone)]
65pub struct Policy {
66    /// Maximum frequency from the BIOS
67    pub bios_limit: Option<u32>,
68
69    /// Core Performance Boost (only for AMD)
70    pub cpb: Option<bool>,
71
72    /// Maximum hardware frequency
73    pub cpu_max_freq: Option<u32>,
74
75    /// Minimum hardware frequency
76    pub cpu_min_freq: Option<u32>,
77
78    /// Time (nsecs) for transition between frequencies
79    pub cpuinfo_transition_latency: Option<bool>,
80
81    /// Available frequencies
82    pub scaling_available_frequencies: Option<Vec<u32>>,
83
84    /// Available frequency governors
85    pub scaling_available_governors: Option<Vec<String>>,
86
87    /// Current core frequency
88    pub scaling_cur_freq: Option<u32>,
89
90    /// Using cpufreq driver
91    pub scaling_driver: Option<String>,
92
93    /// Using governor
94    pub scaling_governor: Option<String>,
95
96    pub scaling_max_freq: Option<u32>,
97    pub scaling_min_freq: Option<u32>,
98    pub scaling_setspeed: Option<String>,
99}
100
101impl Policy {
102    pub fn new(policy: OsString) -> Result<Self> {
103        let dir = Path::new(CPU_FREQ_DIR);
104        let tgt = dir.join(policy);
105        if !dir.exists() || !tgt.exists() {
106            return Err(anyhow!("Directory {} does not exists!", dir.display()));
107        }
108
109        let read = |path: &PathBuf, name: &str| read_to_string(path.join(name));
110        let get_bool = |num: Option<u8>| match num {
111            Some(num) => Some(if num != 0 { true } else { false }),
112            None => None,
113        };
114
115        Ok(Self {
116            bios_limit: read(&tgt, "bios_limit")?.trim().parse().ok(),
117            cpb: get_bool(read(&tgt, "cpb")?.trim().parse().ok()),
118            cpu_max_freq: read(&tgt, "cpuinfo_max_freq")?.trim().parse().ok(),
119            cpu_min_freq: read(&tgt, "cpuinfo_min_freq")?.trim().parse().ok(),
120            cpuinfo_transition_latency: get_bool(
121                read(&tgt, "cpuinfo_transition_latency")?
122                    .trim()
123                    .parse()
124                    .ok(),
125            ),
126            scaling_available_frequencies: Some(
127                read(&tgt, "scaling_available_frequencies")?
128                    .split_whitespace()
129                    .map(|freq| freq.parse::<u32>().ok())
130                    .filter(|freq| freq.is_some())
131                    .map(|freq| freq.unwrap())
132                    .collect::<Vec<_>>(),
133            ),
134            scaling_available_governors: Some(
135                read(&tgt, "scaling_available_governors")?
136                    .trim()
137                    .split_whitespace()
138                    .map(|gov| gov.to_string())
139                    .collect(),
140            ),
141            scaling_cur_freq: read(&tgt, "scaling_cur_freq")?.trim().parse().ok(),
142            scaling_driver: read(&tgt, "scaling_driver")
143                .ok()
144                .and_then(|s| Some(s.trim().to_string())),
145            scaling_governor: read(&tgt, "scaling_governor")
146                .ok()
147                .and_then(|s| Some(s.trim().to_string())),
148            scaling_max_freq: read(&tgt, "scaling_max_freq")?.trim().parse().ok(),
149            scaling_min_freq: read(&tgt, "scaling_min_freq")?.trim().parse().ok(),
150            scaling_setspeed: read(&tgt, "scaling_setspeed")
151                .ok()
152                .and_then(|s| Some(s.trim().to_string())),
153        })
154    }
155}