Skip to main content

auto_cpufreq/
core.rs

1// src/core.rs - OPTIMIZED VERSION
2use crate::power_helper::SYSTEMCTL_EXISTS;
3use anyhow::{bail, Context, Result};
4use chrono::Local;
5use std::collections::HashMap;
6use std::fs::{self, File};
7use std::io::{Read, BufRead, BufReader, Write};
8use std::path::{Path, PathBuf};
9use std::process::Command;
10use std::sync::{Arc, Mutex};
11use std::time::{Duration, Instant};
12use sysinfo::System;
13use std::net::TcpStream;
14use crate::config::CONFIG;
15use crate::globals::AVAILABLE_GOVERNORS_SORTED;
16
17// ============================================================================
18// OPTIMIZATION: Cached System Wrapper
19// ============================================================================
20pub struct CachedSystem {
21    sys: System,
22    last_refresh: Instant,
23    refresh_interval: Duration,
24}
25
26impl CachedSystem {
27    pub fn new(refresh_interval_secs: u64) -> Self {
28        Self {
29            sys: System::new_all(),
30            last_refresh: Instant::now() - Duration::from_secs(999),
31            refresh_interval: Duration::from_secs(refresh_interval_secs),
32        }
33    }
34
35    pub fn get_refreshed_system(&mut self) -> &mut System {
36        if self.last_refresh.elapsed() > self.refresh_interval {
37            self.sys.refresh_cpu_all();
38            std::thread::sleep(Duration::from_millis(200));
39            self.sys.refresh_cpu_all();
40            self.last_refresh = Instant::now();
41        }
42        &mut self.sys
43    }
44
45    pub fn force_refresh(&mut self) {
46        self.sys.refresh_cpu_all();
47        std::thread::sleep(Duration::from_millis(200));
48        self.sys.refresh_cpu_all();
49        self.last_refresh = Instant::now();
50    }
51}
52
53// ============================================================================
54// OPTIMIZATION: Temperature Sensor Cache
55// ============================================================================
56pub struct TempSensorCache {
57    sensor_paths: HashMap<usize, PathBuf>,
58    package_temp_path: Option<PathBuf>,
59    last_scan: Instant,
60}
61
62impl Default for TempSensorCache {
63    fn default() -> Self {
64        Self::new()
65    }
66}
67
68impl TempSensorCache {
69    pub fn new() -> Self {
70        let mut cache = Self {
71            sensor_paths: HashMap::new(),
72            package_temp_path: None,
73            last_scan: Instant::now(),
74        };
75        cache.scan_sensors();
76        cache
77    }
78
79    fn scan_sensors(&mut self) {
80        let sensor_priority = ["coretemp", "k10temp", "zenpower", "acpitz"];
81        let hwmon_path = "/sys/class/hwmon";
82
83        if let Ok(entries) = fs::read_dir(hwmon_path) {
84            for entry in entries.flatten() {
85                let path = entry.path();
86                let name_file = path.join("name");
87
88                if let Ok(sensor_name) = fs::read_to_string(&name_file) {
89                    let sensor_name = sensor_name.trim();
90
91                    if sensor_priority.contains(&sensor_name) {
92                        let pkg_temp = path.join("temp1_input");
93                        if pkg_temp.exists() {
94                            self.package_temp_path = Some(pkg_temp);
95                        }
96
97                        for temp_id in 2..20 {
98                            let temp_file = path.join(format!("temp{}_input", temp_id));
99                            if temp_file.exists() {
100                                let core_id = temp_id - 2;
101                                self.sensor_paths.insert(core_id, temp_file);
102                            }
103                        }
104                        break;
105                    }
106                }
107            }
108        }
109
110        self.last_scan = Instant::now();
111    }
112
113    pub fn read_core_temp(&self, core_id: usize) -> f32 {
114        if let Some(path) = self.sensor_paths.get(&core_id) {
115            if let Ok(temp_str) = fs::read_to_string(path) {
116                if let Ok(temp) = temp_str.trim().parse::<f32>() {
117                    return temp / 1000.0;
118                }
119            }
120        }
121
122        if let Some(ref path) = self.package_temp_path {
123            if let Ok(temp_str) = fs::read_to_string(path) {
124                if let Ok(temp) = temp_str.trim().parse::<f32>() {
125                    return temp / 1000.0;
126                }
127            }
128        }
129
130        0.0
131    }
132
133    pub fn read_package_temp(&self) -> f32 {
134        if let Some(ref path) = self.package_temp_path {
135            if let Ok(temp_str) = fs::read_to_string(path) {
136                if let Ok(temp) = temp_str.trim().parse::<f32>() {
137                    return temp / 1000.0;
138                }
139            }
140        }
141        0.0
142    }
143
144    pub fn maybe_rescan(&mut self) {
145        if self.last_scan.elapsed() > Duration::from_secs(300) {
146            self.scan_sensors();
147        }
148    }
149}
150
151lazy_static::lazy_static! {
152    static ref TEMP_CACHE: Arc<Mutex<TempSensorCache>> = Arc::new(Mutex::new(TempSensorCache::new()));
153    static ref CACHED_SYSTEM: Arc<Mutex<CachedSystem>> = Arc::new(Mutex::new(CachedSystem::new(2)));
154}
155
156// ============================================================================
157// Constants
158// ============================================================================
159const SCRIPTS_DIR: &str = "/usr/local/share/auto-cpufreq/scripts/";
160const POWER_SUPPLY_DIR: &str = "/sys/class/power_supply/";
161pub const CODEBERG: &str = "https://codeberg.org/Zamanhuseyinli/auto-cpufreq-rust";
162
163pub const ALL_GOVERNORS: &[&str] = &[
164    "performance",
165    "ondemand",
166    "conservative",
167    "schedutil",
168    "userspace",
169    "powersave",
170];
171
172fn read_auto_cpufreq_file(sub_path: &str) -> Result<String> {
173    let path = format!("/usr/local/share/auto-cpufreq/scripts/{}", sub_path);
174    fs::read_to_string(&path).with_context(|| {
175        format!(
176            "Required file '{}' not found. Run auto-cpufreq-install.sh first \
177             (it populates /usr/local/share/auto-cpufreq/scripts/) before \
178             using --install/--update/--remove directly.",
179            path
180        )
181    })
182}
183 
184pub fn install_script() -> Result<String> {
185    read_auto_cpufreq_file("auto-cpufreq-install.sh")
186}
187pub fn remove_script() -> Result<String> {
188    read_auto_cpufreq_file("auto-cpufreq-remove.sh")
189}
190pub fn cpufreqctl_script() -> Result<String> {
191    read_auto_cpufreq_file("cpufreqctl.sh")
192}
193pub fn systemd_service() -> Result<String> {
194    read_auto_cpufreq_file("auto-cpufreq.service")
195}
196pub fn openrc_service() -> Result<String> {
197    read_auto_cpufreq_file("auto-cpufreq-openrc")
198}
199pub fn dinit_service() -> Result<String> {
200    read_auto_cpufreq_file("auto-cpufreq-dinit")
201}
202pub fn runit_service() -> Result<String> {
203    read_auto_cpufreq_file("auto-cpufreq-runit")
204}
205pub fn s6_service() -> Result<String> {
206    read_auto_cpufreq_file("auto-cpufreq-s6/run")
207}
208 
209
210// ============================================================================
211// Global state structures
212// ============================================================================
213pub struct AutoCpuFreqState {
214    pub cpu_count: usize,
215    pub performance_load_threshold: f32,
216    pub powersave_load_threshold: f32,
217    pub stats_file_path: PathBuf,
218    pub governor_override_path: PathBuf,
219    pub turbo_override_path: PathBuf,
220    pub is_aur: bool,
221}
222
223impl Default for AutoCpuFreqState {
224    fn default() -> Self {
225        Self::new()
226    }
227}
228
229impl AutoCpuFreqState {
230    pub fn new() -> Self {
231        let cpu_count = num_cpus::get();
232
233        let (stats_path, gov_path, turbo_path) = (
234            PathBuf::from("/var/run/auto-cpufreq.stats"),
235            PathBuf::from("/opt/auto-cpufreq/override.pickle"),
236            PathBuf::from("/opt/auto-cpufreq/turbo-override.pickle"),
237        );
238
239        Self {
240            cpu_count,
241            performance_load_threshold: (50 * cpu_count) as f32 / 100.0,
242            powersave_load_threshold: (75 * cpu_count) as f32 / 100.0,
243            stats_file_path: stats_path,
244            governor_override_path: gov_path,
245            turbo_override_path: turbo_path,
246            is_aur: Self::check_aur_install(),
247        }
248    }
249
250    fn check_aur_install() -> bool {
251        Path::new("/etc/arch-release").exists()
252            && Command::new("pacman")
253                .args(["-Qs", "auto-cpufreq"])
254                .output()
255                .map(|o| !o.stdout.is_empty())
256                .unwrap_or(false)
257    }
258}
259
260// ============================================================================
261// Version management
262// ============================================================================
263pub fn get_version() -> Result<String> {
264    let state = AutoCpuFreqState::new();
265
266    if state.is_aur {
267        let output = Command::new("pacman")
268            .args(["-Qi", "auto-cpufreq"])
269            .output()?;
270        let stdout = String::from_utf8_lossy(&output.stdout);
271
272        stdout
273            .lines()
274            .find(|line| line.contains("Version"))
275            .map(|s| s.to_string())
276            .ok_or_else(|| anyhow::anyhow!("Version not found"))
277    } else {
278        get_formatted_version()
279    }
280}
281
282pub fn get_formatted_version() -> Result<String> {
283    let version = env!("CARGO_PKG_VERSION");
284    Ok(version.to_string())
285}
286
287pub fn app_version() {
288    match get_version() {
289        Ok(v) => println!("auto-cpufreq version: {}", v),
290        Err(e) => eprintln!("Error getting version: {}", e),
291    }
292}
293
294pub fn check_for_update() -> Result<bool> {
295    let host = "codeberg.org";
296    let path = "/api/v1/repos/Zamanhuseyinli/auto-cpufreq-rust/releases/latest";
297
298    let stream = TcpStream::connect((host, 443))?;
299    let connector = native_tls::TlsConnector::new()?;
300    let mut stream = connector.connect(host, stream)?;
301
302    write!(stream, "GET {} HTTP/1.1\r\nHost: {}\r\nUser-Agent: auto-cpufreq-rust\r\nConnection: close\r\n\r\n", path, host)?;
303
304    let mut response = String::new();
305    stream.read_to_string(&mut response)?;
306
307    let body = response
308        .split("\r\n\r\n")
309        .nth(1)
310        .ok_or_else(|| anyhow::anyhow!("Invalid HTTP response"))?;
311
312    let json: serde_json::Value = serde_json::from_str(body)?;
313    let latest = json["tag_name"]
314        .as_str()
315        .ok_or_else(|| anyhow::anyhow!("No tag_name in response"))?;
316    let current = format!("v{}", env!("CARGO_PKG_VERSION"));
317
318    if latest != current {
319        println!("Updates available:");
320        println!("Current version: {}", current);
321        println!("Latest version: {}", latest);
322        Ok(true)
323    } else {
324        println!("auto-cpufreq is up to date");
325        Ok(false)
326    }
327}
328// ============================================================================
329// Governor management
330// ============================================================================
331#[derive(Debug, Clone, PartialEq)]
332pub enum GovernorOverride {
333    Default,
334    Powersave,
335    Performance,
336}
337
338impl std::str::FromStr for GovernorOverride {
339    type Err = ();
340    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
341        match s {
342            "powersave" => Ok(Self::Powersave),
343            "performance" => Ok(Self::Performance),
344            _ => Ok(Self::Default),
345        }
346    }
347}
348
349impl GovernorOverride {
350    pub fn to_str(&self) -> &str {
351        match self {
352            Self::Powersave => "powersave",
353            Self::Performance => "performance",
354            Self::Default => "default",
355        }
356    }
357}
358
359pub fn get_override(state: &AutoCpuFreqState) -> GovernorOverride {
360    if state.governor_override_path.exists() {
361        fs::read_to_string(&state.governor_override_path)
362            .ok()
363            .and_then(|s| s.trim().parse().ok())
364            .unwrap_or(GovernorOverride::Default)
365    } else {
366        GovernorOverride::Default
367    }
368}
369
370pub fn set_override(state: &AutoCpuFreqState, override_val: &str) -> Result<()> {
371    match override_val {
372        "powersave" | "performance" => {
373            fs::write(&state.governor_override_path, override_val)?;
374            println!("Set governor override to {}", override_val);
375        }
376        "reset" => {
377            if state.governor_override_path.exists() {
378                fs::remove_file(&state.governor_override_path)?;
379            }
380            println!("Governor override removed");
381        }
382        _ => {
383            println!("Invalid option.");
384            println!("Use force=performance, force=powersave, or force=reset");
385        }
386    }
387    Ok(())
388}
389
390// ============================================================================
391// Turbo management
392// ============================================================================
393#[derive(Debug, Clone, PartialEq)]
394pub enum TurboOverride {
395    Auto,
396    Never,
397    Always,
398}
399
400impl std::str::FromStr for TurboOverride {
401    type Err = ();
402    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
403        match s {
404            "never" => Ok(Self::Never),
405            "always" => Ok(Self::Always),
406            _ => Ok(Self::Auto),
407        }
408    }
409}
410
411impl TurboOverride {
412    pub fn to_str(&self) -> &str {
413        match self {
414            Self::Never => "never",
415            Self::Always => "always",
416            Self::Auto => "auto",
417        }
418    }
419}
420
421pub fn get_turbo_override(state: &AutoCpuFreqState) -> TurboOverride {
422    if state.turbo_override_path.exists() {
423        fs::read_to_string(&state.turbo_override_path)
424            .ok()
425            .and_then(|s| s.trim().parse().ok())
426            .unwrap_or(TurboOverride::Auto)
427    } else {
428        TurboOverride::Auto
429    }
430}
431
432pub fn set_turbo_override(state: &AutoCpuFreqState, override_val: &str) -> Result<()> {
433    match override_val {
434        "never" | "always" => {
435            fs::write(&state.turbo_override_path, override_val)?;
436            println!("Set turbo boost override to {}", override_val);
437        }
438        "auto" => {
439            if state.turbo_override_path.exists() {
440                fs::remove_file(&state.turbo_override_path)?;
441            }
442            println!("Turbo override removed");
443        }
444        _ => {
445            println!("Invalid option.");
446            println!("Use turbo=always, turbo=never, or turbo=auto");
447        }
448    }
449    Ok(())
450}
451
452pub fn turbo(value: Option<bool>) -> Result<bool> {
453    let p_state = Path::new("/sys/devices/system/cpu/intel_pstate/no_turbo");
454    let cpufreq = Path::new("/sys/devices/system/cpu/cpufreq/boost");
455    let amd_pstate = Path::new("/sys/devices/system/cpu/amd_pstate/status");
456
457    let (control_file, inverse) = if p_state.exists() {
458        (p_state, true)
459    } else if cpufreq.exists() {
460        (cpufreq, false)
461    } else if amd_pstate.exists() {
462        let status = fs::read_to_string(amd_pstate)?.trim().to_string();
463        if status == "active" {
464            println!("CPU turbo is controlled by amd-pstate-epp driver");
465        }
466        return Ok(false);
467    } else {
468        println!("Warning: CPU turbo is not available");
469        return Ok(false);
470    };
471
472    if let Some(val) = value {
473        let write_val = if inverse { !val } else { val };
474        match fs::write(control_file, format!("{}\n", write_val as u8)) {
475            Ok(_) => {}
476            Err(_) => {
477                println!("Warning: Changing CPU turbo is not supported. Skipping.");
478                return Ok(false);
479            }
480        }
481    }
482
483    let current = fs::read_to_string(control_file)?.trim().parse::<u8>()?;
484
485    Ok((current != 0) ^ inverse)
486}
487
488pub fn get_turbo() {
489    match turbo(None) {
490        Ok(state) => println!(
491            "Currently turbo boost is: {}",
492            if state { "on" } else { "off" }
493        ),
494        Err(e) => eprintln!("Error getting turbo state: {}", e),
495    }
496}
497
498pub fn set_turbo(value: bool) {
499    println!("Setting turbo boost: {}", if value { "on" } else { "off" });
500    let _ = turbo(Some(value));
501}
502
503// ============================================================================
504// Distribution info
505// ============================================================================
506pub fn distro_info() -> Result<()> {
507    let mut dist_name = "UNKNOWN distro".to_string();
508    let mut version = "UNKNOWN version".to_string();
509
510    if Path::new("/etc/os-release").exists() {
511        let file = File::open("/etc/os-release")?;
512        let reader = BufReader::new(file);
513
514        for line in reader.lines() {
515            let line = line?;
516            if line.starts_with("NAME=") {
517                dist_name = line
518                    .trim_start_matches("NAME=")
519                    .trim_matches('"')
520                    .to_string();
521            } else if line.starts_with("VERSION=") {
522                version = line
523                    .trim_start_matches("VERSION=")
524                    .trim_matches('"')
525                    .to_string();
526            }
527        }
528    }
529
530    println!("Linux distro: {} {}", dist_name, version);
531    println!(
532        "Linux kernel: {}",
533        System::kernel_version().unwrap_or_default()
534    );
535
536    Ok(())
537}
538
539// ============================================================================
540// OPTIMIZED: Temperature reading functions
541// ============================================================================
542pub fn read_cpu_temperature(core_id: usize) -> f32 {
543    TEMP_CACHE.lock().unwrap().read_core_temp(core_id)
544}
545
546pub fn read_package_temperature() -> f32 {
547    TEMP_CACHE.lock().unwrap().read_package_temp()
548}
549
550// ============================================================================
551// System info
552// ============================================================================
553pub fn sysinfo() -> Result<()> {
554    let cpuinfo = fs::read_to_string("/proc/cpuinfo")?;
555    let model_name = cpuinfo
556        .lines()
557        .find(|line| line.contains("model name"))
558        .and_then(|line| line.split(':').nth(1))
559        .map(|s| s.trim())
560        .unwrap_or("Unknown");
561
562    println!("Processor: {}", model_name);
563
564    let cpu_count = num_cpus::get();
565    println!("Cores: {}", cpu_count);
566
567    let arch = std::env::consts::ARCH;
568    println!("Architecture: {}", arch);
569
570    let driver = fs::read_to_string("/sys/devices/system/cpu/cpu0/cpufreq/scaling_driver")
571        .unwrap_or_else(|_| "unknown".to_string())
572        .trim()
573        .to_string();
574    println!("Driver: {}", driver);
575
576    let mut cached_sys = CACHED_SYSTEM.lock().unwrap();
577    let sys = cached_sys.get_refreshed_system();
578
579    if let Some(cpu) = sys.cpus().first() {
580        println!(
581            "\n{}",
582            "-".repeat(30) + " Current CPU stats " + &"-".repeat(30)
583        );
584        println!("\nCPU max frequency: {:.0} MHz", cpu.frequency());
585    }
586
587    println!(
588        "\n{:<6} {:<8} {:<16} {:<10}",
589        "Core", "Usage", "Temperature", "Frequency"
590    );
591
592    for (i, cpu) in sys.cpus().iter().enumerate() {
593        let temp = read_cpu_temperature(i);
594        let temp_str = if temp > 0.0 {
595            format!("{:.0} °C", temp)
596        } else {
597            "-- °C".to_string()
598        };
599
600        println!(
601            "{:<6} {:<8.1}% {:<16} {:.0} MHz",
602            format!("CPU{}", i),
603            cpu.cpu_usage(),
604            temp_str,
605            cpu.frequency()
606        );
607    }
608
609    let pkg_temp = read_package_temperature();
610    if pkg_temp > 0.0 {
611        println!("\nPackage temperature: {:.1} °C", pkg_temp);
612    }
613
614    Ok(())
615}
616
617// ============================================================================
618// Power supply / charging detection
619// ============================================================================
620pub fn get_power_supply_ignore_list() -> Vec<String> {
621    vec!["hidpp_battery".to_string()]
622}
623
624pub fn charging() -> Result<bool> {
625    let power_dir = Path::new(POWER_SUPPLY_DIR);
626
627    if !power_dir.exists() {
628        return Ok(true);
629    }
630
631    let mut entries: Vec<_> = fs::read_dir(power_dir)?.filter_map(|e| e.ok()).collect();
632    entries.sort_by_key(|e| e.file_name());
633
634    let ignore_list = get_power_supply_ignore_list();
635
636    if entries.is_empty() {
637        return Ok(true);
638    }
639
640    for entry in entries {
641        let name = entry.file_name();
642        let name_str = name.to_string_lossy();
643
644        if ignore_list.iter().any(|ignored| name_str.contains(ignored)) {
645            continue;
646        }
647
648        let supply_path = entry.path();
649        let type_path = supply_path.join("type");
650
651        if !type_path.exists() {
652            continue;
653        }
654
655        let supply_type = fs::read_to_string(&type_path)?.trim().to_string();
656
657        if supply_type == "Mains" {
658            let online_path = supply_path.join("online");
659            if online_path.exists() {
660                let online = fs::read_to_string(&online_path)?.trim().to_string();
661                if online == "1" {
662                    return Ok(true);
663                }
664            }
665        } else if supply_type == "Battery" {
666            let status_path = supply_path.join("status");
667            if status_path.exists() {
668                let status = fs::read_to_string(&status_path)?.trim().to_string();
669                if status == "Discharging" {
670                    return Ok(false);
671                }
672            }
673        }
674    }
675
676    Ok(true)
677}
678
679// ============================================================================
680// Governor functions
681// ============================================================================
682pub fn get_current_gov() -> Result<String> {
683    let output = Command::new("cpufreqctl.auto-cpufreq")
684        .arg("--governor")
685        .output()?;
686
687    let stdout = String::from_utf8_lossy(&output.stdout);
688    let gov = stdout.split_whitespace().next().unwrap_or("unknown");
689
690    Ok(gov.to_string())
691}
692
693pub fn print_current_gov() {
694    match get_current_gov() {
695        Ok(gov) => println!("Currently using: {} governor", gov),
696        Err(e) => eprintln!("Error getting governor: {}", e),
697    }
698}
699
700// ============================================================================
701// cpufreqctl deployment
702// ============================================================================
703pub fn cpufreqctl() -> Result<()> {
704    let target = "/usr/local/bin/cpufreqctl.auto-cpufreq";
705 
706    if !Path::new(target).exists() {
707        let source = PathBuf::from(SCRIPTS_DIR).join("cpufreqctl.sh");
708        if !source.exists() {
709            bail!(
710                "Required file '{}' not found. Run auto-cpufreq-install.sh first.",
711                source.display()
712            );
713        }
714        fs::copy(source, target)?;
715 
716        Command::new("chmod").args(["a+x", target]).status()?;
717    }
718 
719    Ok(())
720}
721 
722pub fn cpufreqctl_restore() -> Result<()> {
723    let target = "/usr/local/bin/cpufreqctl.auto-cpufreq";
724 
725    if Path::new(target).exists() {
726        fs::remove_file(target)?;
727    }
728 
729    Ok(())
730}
731 
732fn deploy_cpufreqctl() -> Result<()> {
733    let target = "/usr/local/bin/cpufreqctl.auto-cpufreq";
734 
735    if !Path::new(target).exists() {
736        println!("\n* Deploying cpufreqctl helper script");
737        fs::write(target, cpufreqctl_script()?)?;
738 
739        Command::new("chmod").args(["+x", target]).status()?;
740    }
741 
742    Ok(())
743}
744 
745fn remove_cpufreqctl() -> Result<()> {
746    let target = "/usr/local/bin/cpufreqctl.auto-cpufreq";
747 
748    if Path::new(target).exists() {
749        println!("\n* Removing cpufreqctl helper script");
750        fs::remove_file(target)?;
751    }
752 
753    Ok(())
754}
755 
756
757// ============================================================================
758// Stats file update function
759// ============================================================================
760pub fn update_stats_file() -> Result<()> {
761    let state = AutoCpuFreqState::new();
762
763    if let Some(parent) = state.stats_file_path.parent() {
764        fs::create_dir_all(parent)?;
765    }
766
767    let mut stats = String::with_capacity(2048);
768
769    use std::fmt::Write as FmtWrite;
770
771    let _ = writeln!(&mut stats, "\n{}", "=".repeat(80));
772    let _ = writeln!(
773        &mut stats,
774        "auto-cpufreq daemon - {}",
775        Local::now().format("%Y-%m-%d %H:%M:%S")
776    );
777    let _ = writeln!(&mut stats, "{}\n", "=".repeat(80));
778
779    let mut cached_sys = CACHED_SYSTEM.lock().unwrap();
780    let sys = cached_sys.get_refreshed_system();
781
782    let cpu_usage: f32 =
783        sys.cpus().iter().map(|c| c.cpu_usage()).sum::<f32>() / sys.cpus().len() as f32;
784
785    let loadavg = System::load_average();
786
787    let _ = writeln!(&mut stats, "CPU usage: {:.1}%", cpu_usage);
788    let _ = writeln!(
789        &mut stats,
790        "Load: {:.2}, {:.2}, {:.2}",
791        loadavg.one, loadavg.five, loadavg.fifteen
792    );
793
794    if let Ok(gov) = get_current_gov() {
795        let _ = writeln!(&mut stats, "Governor: {}", gov);
796    }
797
798    if let Ok(turbo_state) = turbo(None) {
799        let _ = writeln!(
800            &mut stats,
801            "Turbo: {}",
802            if turbo_state { "On" } else { "Off" }
803        );
804    }
805
806    if let Ok(is_charging) = charging() {
807        let _ = writeln!(
808            &mut stats,
809            "Battery: {}",
810            if is_charging {
811                "Charging"
812            } else {
813                "Discharging"
814            }
815        );
816    }
817
818    let _ = writeln!(&mut stats, "\n{}", "-".repeat(80));
819
820    fs::write(&state.stats_file_path, stats)?;
821
822    Ok(())
823}
824
825// ============================================================================
826// Load information
827// ============================================================================
828pub fn get_load() -> (f64, f64) {
829    let mut cached_sys = CACHED_SYSTEM.lock().unwrap();
830    let sys = cached_sys.get_refreshed_system();
831
832    let cpu_usage: f64 = sys
833        .cpus()
834        .iter()
835        .map(|cpu| cpu.cpu_usage() as f64)
836        .sum::<f64>()
837        / sys.cpus().len() as f64;
838
839    let loadavg = System::load_average();
840    let load1m = loadavg.one;
841
842    println!("\nTotal CPU usage: {:.1}%", cpu_usage);
843    println!("Total system load: {:.2}", load1m);
844
845    let temp_cache = TEMP_CACHE.lock().unwrap();
846    let temps: Vec<f32> = (0..sys.cpus().len())
847        .map(|i| temp_cache.read_core_temp(i))
848        .filter(|&t| t > 0.0)
849        .collect();
850
851    if !temps.is_empty() {
852        let avg_temp: f32 = temps.iter().sum::<f32>() / temps.len() as f32;
853        println!("Average temp. of all cores: {:.1} °C\n", avg_temp);
854    } else {
855        println!("Average temp. of all cores: -- °C\n");
856    }
857
858    (cpu_usage, load1m)
859}
860
861pub fn display_system_load_avg() {
862    let loadavg = System::load_average();
863    println!(
864        " (load average: {:.2}, {:.2}, {:.2})",
865        loadavg.one, loadavg.five, loadavg.fifteen
866    );
867}
868
869// ============================================================================
870// Utility functions
871// ============================================================================
872pub fn footer(length: usize) {
873    println!("\n{}\n", "-".repeat(length));
874}
875
876pub fn root_check() -> Result<()> {
877    if !nix::unistd::Uid::effective().is_root() {
878        eprintln!("\n{}\n", "-".repeat(33) + " Root check " + &"-".repeat(34));
879        eprintln!("ERROR:\n");
880        eprintln!("Must be run as root for this functionality to work");
881        bail!("Not running as root");
882    }
883    Ok(())
884}
885
886pub fn countdown(seconds: u64) {
887    use std::io::stdout;
888
889    std::env::set_var("TERM", "xterm");
890
891    print!("\t\t\"auto-cpufreq\" is about to refresh ");
892    stdout().flush().unwrap();
893
894    for remaining in (0..=seconds).rev() {
895        if remaining <= 3 {
896            print!(".");
897            stdout().flush().unwrap();
898        }
899        std::thread::sleep(std::time::Duration::from_millis(1000 * seconds / 3));
900    }
901
902    println!("\n\t\tExecuted on: {}", Local::now().format("%c"));
903}
904
905// ============================================================================
906// OPTIMIZED: Improved daemon detection
907// ============================================================================
908pub fn is_running(program: &str, argument: &str) -> bool {
909    if let Ok(output) = Command::new("pidof").arg("-x").arg(program).output() {
910        if !output.stdout.is_empty() {
911            return check_proc_daemon_status(program, argument);
912        }
913    }
914
915    check_proc_daemon_status(program, argument)
916}
917
918fn check_proc_daemon_status(program: &str, argument: &str) -> bool {
919    let proc_path = Path::new("/proc");
920
921    if !proc_path.exists() {
922        return is_running_sysinfo(program, argument);
923    }
924
925    let entries = match fs::read_dir(proc_path) {
926        Ok(e) => e,
927        Err(_) => return is_running_sysinfo(program, argument),
928    };
929
930    for entry in entries.filter_map(|e| e.ok()) {
931        let path = entry.path();
932        let file_name = entry.file_name();
933        let file_name_str = file_name.to_string_lossy();
934
935        if !file_name_str.chars().all(|c| c.is_numeric()) {
936            continue;
937        }
938
939        let cmdline_path = path.join("cmdline");
940        if let Ok(cmdline_bytes) = fs::read(&cmdline_path) {
941            let cmdline_str = String::from_utf8_lossy(&cmdline_bytes);
942            let args: Vec<&str> = cmdline_str.split('\0').filter(|s| !s.is_empty()).collect();
943
944            if args.is_empty() {
945                continue;
946            }
947
948            let has_program = args.iter().any(|arg| arg.contains(program));
949
950            if has_program {
951                let has_argument = args.iter().any(|arg| arg.contains(argument));
952
953                if has_argument {
954                    return true;
955                }
956            }
957        }
958    }
959
960    false
961}
962
963fn is_running_sysinfo(program: &str, argument: &str) -> bool {
964    let mut sys = System::new();
965    sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true);
966
967    for process in sys.processes().values() {
968        let exe_path = process.exe().and_then(|p| p.to_str()).unwrap_or("");
969        let cmd = process.cmd();
970        let name = process.name();
971
972        let has_program = name.to_str().is_some_and(|s| s.contains(program))
973            || exe_path.contains(program)
974            || exe_path.contains(program)
975        || cmd.iter().any(|s| s.to_str().is_some_and(|t| t.contains(program)));
976
977        let has_argument = cmd.iter().any(|s| s.to_str().is_some_and(|t| t.contains(argument)));
978
979        if has_program && has_argument {
980            return true;
981        }
982    }
983
984    false
985}
986
987pub fn daemon_running_check() -> Result<()> {
988    if is_running("auto-cpufreq", "--daemon") {
989        println!(
990            "\n{}\n",
991            "-".repeat(24) + " auto-cpufreq running " + &"-".repeat(30)
992        );
993        println!("ERROR: auto-cpufreq is running in daemon mode.");
994        println!("\nMake sure to stop the daemon before running with --live or --monitor mode");
995        footer(79);
996        bail!("Daemon already running");
997    }
998
999    Ok(())
1000}
1001
1002pub fn not_running_daemon_check() -> Result<()> {
1003    if !is_running("auto-cpufreq", "--daemon") {
1004        if *SYSTEMCTL_EXISTS {
1005            let status = Command::new("systemctl")
1006                .args(["is-active", "auto-cpufreq"])
1007                .output();
1008
1009            if let Ok(out) = status {
1010                let active = String::from_utf8_lossy(&out.stdout);
1011                if active.trim() == "active" {
1012                    return Ok(());
1013                }
1014            }
1015        }
1016
1017        println!(
1018            "\n{}\n",
1019            "-".repeat(24) + " auto-cpufreq not running " + &"-".repeat(30)
1020        );
1021        println!("ERROR: auto-cpufreq is not running in daemon mode.");
1022        println!("\nMake sure to run \"sudo auto-cpufreq --install\" first");
1023        footer(79);
1024        bail!("Daemon not running");
1025    }
1026
1027    Ok(())
1028}
1029
1030// ============================================================================
1031// Install/Remove script runners
1032// ============================================================================
1033pub fn run_install_script() -> Result<()> {
1034    println!("\n* Running pre-installation script");
1035 
1036    let script_contents = install_script()?;
1037 
1038    let temp_script = "/tmp/auto-cpufreq-install.sh";
1039    fs::write(temp_script, script_contents)?;
1040 
1041    Command::new("chmod").args(["+x", temp_script]).status()?;
1042 
1043    let status = Command::new("bash").arg(temp_script).status()?;
1044 
1045    let _ = fs::remove_file(temp_script);
1046 
1047    if status.success() {
1048        println!("* Pre-installation script completed successfully");
1049        Ok(())
1050    } else {
1051        bail!("Pre-installation script exited with an error");
1052    }
1053}
1054 
1055pub fn run_remove_script() -> Result<()> {
1056    println!("\n* Running post-removal script");
1057 
1058    let script_contents = remove_script()?;
1059 
1060    let temp_script = "/tmp/auto-cpufreq-remove.sh";
1061    fs::write(temp_script, script_contents)?;
1062 
1063    Command::new("chmod").args(["+x", temp_script]).status()?;
1064 
1065    let status = Command::new("bash").arg(temp_script).status()?;
1066 
1067    let _ = fs::remove_file(temp_script);
1068 
1069    if status.success() {
1070        println!("* Post-removal script completed successfully");
1071        Ok(())
1072    } else {
1073        bail!("Post-removal script exited with an error");
1074    }
1075}
1076 
1077pub fn get_install_script() -> Result<String> {
1078    install_script()
1079}
1080pub fn get_remove_script() -> Result<String> {
1081    remove_script()
1082}
1083
1084// ============================================================================
1085// Init system detection and daemon installation/removal
1086// ============================================================================
1087pub fn detect_init_system() -> &'static str {
1088    let output = Command::new("ps").args(["-p", "1", "-o", "comm="]).output();
1089
1090    if let Ok(out) = output {
1091        let init = String::from_utf8_lossy(&out.stdout).trim().to_string();
1092        match init.as_str() {
1093            "systemd" => "systemd",
1094            "init" => "openrc",
1095            "dinit" => "dinit",
1096            "runit" => "runit",
1097            "s6-svscan" => "s6",
1098            _ => "unknown",
1099        }
1100    } else {
1101        "unknown"
1102    }
1103}
1104
1105pub fn install_daemon() -> Result<()> {
1106    let init = detect_init_system();
1107
1108    println!("\n{}", "=".repeat(80));
1109    println!("Installing auto-cpufreq daemon ({} detected)", init);
1110    println!("{}", "=".repeat(80));
1111
1112    run_install_script()?;
1113    deploy_cpufreqctl()?;
1114
1115    match init {
1116        "systemd" => install_systemd(),
1117        "openrc" => install_openrc(),
1118        "dinit" => install_dinit(),
1119        "runit" => install_runit(),
1120        "s6" => install_s6(),
1121        _ => {
1122            println!("\n* Unsupported init system detected, could not install the daemon\n");
1123            println!(
1124                "* Please open an issue on https://codeberg.org/Zamanhuseyinli/auto-cpufreq-rust\n"
1125            );
1126            bail!("Unsupported init system: {}", init)
1127        }
1128    }
1129}
1130
1131pub fn remove_daemon() -> Result<()> {
1132    let init = detect_init_system();
1133
1134    println!("\n{}", "=".repeat(80));
1135    println!("Removing auto-cpufreq daemon ({} detected)", init);
1136    println!("{}", "=".repeat(80));
1137
1138    let result = match init {
1139        "systemd" => remove_systemd(),
1140        "openrc" => remove_openrc(),
1141        "dinit" => remove_dinit(),
1142        "runit" => remove_runit(),
1143        "s6" => remove_s6(),
1144        _ => {
1145            println!("\n* Unsupported init system detected, could not remove the daemon");
1146            println!(
1147                "* Please open an issue on https://codeberg.org/Zamanhuseyinli/auto-cpufreq-rust\n"
1148            );
1149            bail!("Unsupported init system: {}", init)
1150        }
1151    };
1152
1153    remove_cpufreqctl()?;
1154    run_remove_script()?;
1155
1156    result
1157}
1158
1159// ============================================================================
1160// systemd
1161// ============================================================================
1162fn install_systemd() -> Result<()> {
1163    println!("\n* Deploying auto-cpufreq systemd unit file");
1164 
1165    fs::write(
1166        "/etc/systemd/system/auto-cpufreq.service",
1167        systemd_service()?,
1168    )?;
1169 
1170    println!("\n* Reloading systemd manager configuration");
1171    Command::new("systemctl").arg("daemon-reload").status()?;
1172 
1173    println!("\n* Starting auto-cpufreq daemon (systemd) service");
1174    Command::new("systemctl")
1175        .args(["start", "auto-cpufreq"])
1176        .status()?;
1177 
1178    println!("\n* Enabling auto-cpufreq daemon (systemd) at boot");
1179    Command::new("systemctl")
1180        .args(["enable", "auto-cpufreq"])
1181        .status()?;
1182 
1183    Ok(())
1184}
1185
1186fn remove_systemd() -> Result<()> {
1187    println!("\n* Stopping auto-cpufreq daemon (systemd) service");
1188    let _ = Command::new("systemctl")
1189        .args(["stop", "auto-cpufreq"])
1190        .status();
1191
1192    println!("\n* Disabling auto-cpufreq daemon (systemd) at boot");
1193    let _ = Command::new("systemctl")
1194        .args(["disable", "auto-cpufreq"])
1195        .status();
1196
1197    println!("\n* Removing auto-cpufreq daemon (systemd) unit file");
1198    let _ = fs::remove_file("/etc/systemd/system/auto-cpufreq.service");
1199
1200    println!("\n* Reloading systemd manager configuration");
1201    Command::new("systemctl").arg("daemon-reload").status()?;
1202
1203    println!("\nReset failed");
1204    Command::new("systemctl").arg("reset-failed").status()?;
1205
1206    Ok(())
1207}
1208
1209// ============================================================================
1210// OpenRC
1211// ============================================================================
1212fn install_openrc() -> Result<()> {
1213    println!("\n* Deploying auto-cpufreq openrc unit file");
1214 
1215    fs::write("/etc/init.d/auto-cpufreq", openrc_service()?)?;
1216 
1217    Command::new("chmod")
1218        .args(["+x", "/etc/init.d/auto-cpufreq"])
1219        .status()?;
1220 
1221    println!("\n* Starting auto-cpufreq daemon (openrc) service");
1222    Command::new("rc-service")
1223        .args(["auto-cpufreq", "start"])
1224        .status()?;
1225 
1226    println!("\n* Enabling auto-cpufreq daemon (openrc) at boot");
1227    Command::new("rc-update")
1228        .args(["add", "auto-cpufreq"])
1229        .status()?;
1230 
1231    Ok(())
1232}
1233fn remove_openrc() -> Result<()> {
1234    println!("\n* Stopping auto-cpufreq daemon (openrc) service");
1235    let _ = Command::new("rc-service")
1236        .args(["auto-cpufreq", "stop"])
1237        .status();
1238
1239    println!("\n* Disabling auto-cpufreq daemon (openrc) at boot");
1240    let _ = Command::new("rc-update")
1241        .args(["del", "auto-cpufreq"])
1242        .status();
1243
1244    println!("\n* Removing auto-cpufreq daemon (openrc) unit file");
1245    let _ = fs::remove_file("/etc/init.d/auto-cpufreq");
1246
1247    Ok(())
1248}
1249
1250// ============================================================================
1251// dinit
1252// ============================================================================
1253fn install_dinit() -> Result<()> {
1254    println!("\n* Deploying auto-cpufreq (dinit) unit file");
1255 
1256    fs::write("/etc/dinit.d/auto-cpufreq", dinit_service()?)?;
1257 
1258    println!("\n* Starting auto-cpufreq daemon (dinit) service");
1259    Command::new("dinitctl")
1260        .args(["start", "auto-cpufreq"])
1261        .status()?;
1262 
1263    println!("\n* Enabling auto-cpufreq daemon (dinit) at boot");
1264    Command::new("dinitctl")
1265        .args(["enable", "auto-cpufreq"])
1266        .status()?;
1267 
1268    Ok(())
1269}
1270fn remove_dinit() -> Result<()> {
1271    println!("\n* Stopping auto-cpufreq daemon (dinit) service");
1272    let _ = Command::new("dinitctl")
1273        .args(["stop", "auto-cpufreq"])
1274        .status();
1275
1276    println!("\n* Disabling auto-cpufreq daemon (dinit) at boot");
1277    let _ = Command::new("dinitctl")
1278        .args(["disable", "auto-cpufreq"])
1279        .status();
1280
1281    println!("\n* Removing auto-cpufreq daemon (dinit) unit file");
1282    let _ = fs::remove_file("/etc/dinit.d/auto-cpufreq");
1283
1284    Ok(())
1285}
1286
1287// ============================================================================
1288// runit
1289// ============================================================================
1290fn install_runit() -> Result<()> {
1291    let (sv_path, service_path) = if Path::new("/etc/os-release").exists() {
1292        let os_release = fs::read_to_string("/etc/os-release")?;
1293        let mut distro_id = String::new();
1294 
1295        for line in os_release.lines() {
1296            if line.starts_with("ID=") {
1297                distro_id = line.trim_start_matches("ID=").trim_matches('"').to_string();
1298                break;
1299            }
1300        }
1301 
1302        match distro_id.as_str() {
1303            "void" => ("/etc", "/var"),
1304            "artix" => ("/etc/runit", "/run/runit"),
1305            _ => {
1306                println!("\n* Runit init detected but your distro is not supported\n");
1307                println!("* Please open an issue on https://codeberg.org/Zamanhuseyinli/auto-cpufreq-rust\n");
1308                bail!("Unsupported runit distro: {}", distro_id);
1309            }
1310        }
1311    } else {
1312        bail!("Could not detect distro for runit");
1313    };
1314 
1315    println!("\n* Deploying auto-cpufreq (runit) unit file");
1316 
1317    let sv_dir = format!("{}/sv/auto-cpufreq", sv_path);
1318    fs::create_dir_all(&sv_dir)?;
1319 
1320    let run_script = format!("{}/run", sv_dir);
1321    fs::write(&run_script, runit_service()?)?;
1322 
1323    Command::new("chmod").args(["+x", &run_script]).status()?;
1324 
1325    println!(
1326        "\n* Creating symbolic link ({}/service/auto-cpufreq -> {}/sv/auto-cpufreq)",
1327        service_path, sv_path
1328    );
1329 
1330    let service_link = format!("{}/service/auto-cpufreq", service_path);
1331    let _ = fs::remove_file(&service_link);
1332 
1333    std::os::unix::fs::symlink(&sv_dir, &service_link)?;
1334 
1335    println!("\n* Starting auto-cpufreq daemon (runit)");
1336    Command::new("sv")
1337        .args(["start", "auto-cpufreq"])
1338        .status()?;
1339 
1340    Command::new("sv").args(["up", "auto-cpufreq"]).status()?;
1341 
1342    Ok(())
1343}
1344fn remove_runit() -> Result<()> {
1345    let (sv_path, service_path) = if Path::new("/etc/os-release").exists() {
1346        let os_release = fs::read_to_string("/etc/os-release")?;
1347        let mut distro_id = String::new();
1348
1349        for line in os_release.lines() {
1350            if line.starts_with("ID=") {
1351                distro_id = line.trim_start_matches("ID=").trim_matches('"').to_string();
1352                break;
1353            }
1354        }
1355
1356        match distro_id.as_str() {
1357            "void" => ("/etc", "/var"),
1358            "artix" => ("/etc/runit", "/run/runit"),
1359            _ => bail!("Unsupported runit distro"),
1360        }
1361    } else {
1362        bail!("Could not detect distro");
1363    };
1364
1365    println!("\n* Stopping auto-cpufreq daemon (runit) service");
1366    let _ = Command::new("sv").args(["stop", "auto-cpufreq"]).status();
1367
1368    println!("\n* Removing auto-cpufreq daemon (runit) unit file");
1369    let _ = fs::remove_dir_all(format!("{}/sv/auto-cpufreq", sv_path));
1370    let _ = fs::remove_file(format!("{}/service/auto-cpufreq", service_path));
1371
1372    Ok(())
1373}
1374
1375// ============================================================================
1376// s6
1377// ============================================================================
1378fn install_s6() -> Result<()> {
1379    println!("\n* Deploying auto-cpufreq (s6) unit file");
1380 
1381    let s6_dir = "/etc/s6/sv/auto-cpufreq";
1382    fs::create_dir_all(s6_dir)?;
1383 
1384    let run_script = format!("{}/run", s6_dir);
1385    fs::write(&run_script, s6_service()?)?;
1386 
1387    Command::new("chmod").args(["+x", &run_script]).status()?;
1388 
1389    println!("\n* Add auto-cpufreq service (s6) to default bundle");
1390    Command::new("s6-service")
1391        .args(["add", "default", "auto-cpufreq"])
1392        .status()?;
1393 
1394    println!("\n* Starting auto-cpufreq daemon (s6)");
1395    Command::new("s6-rc")
1396        .args(["-u", "change", "auto-cpufreq", "default"])
1397        .status()?;
1398 
1399    println!("\n* Update daemon service bundle (s6)");
1400    Command::new("s6-db-reload").status()?;
1401 
1402    Ok(())
1403}
1404fn remove_s6() -> Result<()> {
1405    println!("\n* Disabling auto-cpufreq daemon (s6) at boot");
1406    let _ = Command::new("s6-service")
1407        .args(["delete", "default", "auto-cpufreq"])
1408        .status();
1409
1410    println!("\n* Removing auto-cpufreq daemon (s6) unit file");
1411    let _ = fs::remove_dir_all("/etc/s6/sv/auto-cpufreq");
1412
1413    println!("\n* Update daemon service bundle (s6)");
1414    Command::new("s6-db-reload").status()?;
1415
1416    Ok(())
1417}
1418
1419// ============================================================================
1420// Automatic frequency adjustment - Main daemon logic
1421// ============================================================================
1422fn get_appropriate_governor(is_charging: bool, cpu_usage: f32, load: f32) -> &'static str {
1423    let state = AutoCpuFreqState::new();
1424    let override_val = get_override(&state);
1425
1426    match override_val {
1427        GovernorOverride::Performance => return "performance",
1428        GovernorOverride::Powersave => return "powersave",
1429        GovernorOverride::Default => {}
1430    }
1431
1432    if CONFIG.has_option("charger", "governor") && is_charging {
1433        let gov = CONFIG.get("charger", "governor", "");
1434        if !gov.is_empty() && AVAILABLE_GOVERNORS_SORTED.iter().any(|g| g == &gov) {
1435            if let Some(g) = AVAILABLE_GOVERNORS_SORTED.iter().find(|&x| x == &gov) {
1436                return g.as_str();
1437            }
1438        }
1439    }
1440
1441    if CONFIG.has_option("battery", "governor") && !is_charging {
1442        let gov = CONFIG.get("battery", "governor", "");
1443        if !gov.is_empty() && AVAILABLE_GOVERNORS_SORTED.iter().any(|g| g == &gov) {
1444            if let Some(g) = AVAILABLE_GOVERNORS_SORTED.iter().find(|&x| x == &gov) {
1445                return g.as_str();
1446            }
1447        }
1448    }
1449
1450    if is_charging {
1451        if (cpu_usage > 50.0 || load > state.performance_load_threshold)
1452            && AVAILABLE_GOVERNORS_SORTED.contains(&"performance".to_string())
1453        {
1454            return "performance";
1455        }
1456        if AVAILABLE_GOVERNORS_SORTED.contains(&"schedutil".to_string()) {
1457            return "schedutil";
1458        } else if AVAILABLE_GOVERNORS_SORTED.contains(&"ondemand".to_string()) {
1459            return "ondemand";
1460        }
1461    } else {
1462        if cpu_usage < 25.0
1463            && load < state.powersave_load_threshold
1464            && AVAILABLE_GOVERNORS_SORTED.contains(&"powersave".to_string())
1465        {
1466            return "powersave";
1467        }
1468        if AVAILABLE_GOVERNORS_SORTED.contains(&"schedutil".to_string()) {
1469            return "schedutil";
1470        }
1471    }
1472
1473    AVAILABLE_GOVERNORS_SORTED
1474        .first()
1475        .map(|s| s.as_str())
1476        .unwrap_or("schedutil")
1477}
1478
1479fn set_governor(governor: &str) -> Result<()> {
1480    println!("Setting governor: {}", governor);
1481    let status = Command::new("cpufreqctl.auto-cpufreq")
1482        .arg("--governor")
1483        .arg(format!("--set={}", governor))
1484        .status()
1485        .context("Failed to set governor")?;
1486    if !status.success() {
1487        bail!("Governor change failed");
1488    }
1489    Ok(())
1490}
1491fn set_turbo_based_on_usage(cpu_usage: f32, is_charging: bool) -> Result<()> {
1492    let state = AutoCpuFreqState::new();
1493    let turbo_override = get_turbo_override(&state);
1494
1495    match turbo_override {
1496        TurboOverride::Always => {
1497            set_turbo(true);
1498            return Ok(());
1499        }
1500        TurboOverride::Never => {
1501            set_turbo(false);
1502            return Ok(());
1503        }
1504        TurboOverride::Auto => {}
1505    }
1506
1507    if CONFIG.has_option("charger", "turbo") && is_charging {
1508        let turbo_conf = CONFIG.get("charger", "turbo", "auto");
1509        match turbo_conf.as_str() {
1510            "always" => {
1511                set_turbo(true);
1512                return Ok(());
1513            }
1514            "never" => {
1515                set_turbo(false);
1516                return Ok(());
1517            }
1518            _ => {}
1519        }
1520    }
1521
1522    if CONFIG.has_option("battery", "turbo") && !is_charging {
1523        let turbo_conf = CONFIG.get("battery", "turbo", "auto");
1524        match turbo_conf.as_str() {
1525            "always" => {
1526                set_turbo(true);
1527                return Ok(());
1528            }
1529            "never" => {
1530                set_turbo(false);
1531                return Ok(());
1532            }
1533            _ => {}
1534        }
1535    }
1536
1537    let mut cached_sys = CACHED_SYSTEM.lock().unwrap();
1538    let sys = cached_sys.get_refreshed_system();
1539
1540    let temp_cache = TEMP_CACHE.lock().unwrap();
1541    let cores = (0..sys.cpus().len())
1542        .map(|i| temp_cache.read_core_temp(i))
1543        .filter(|&t| t > 0.0)
1544        .collect::<Vec<_>>();
1545
1546    let avg_temp = if !cores.is_empty() {
1547        cores.iter().sum::<f32>() / cores.len() as f32
1548    } else {
1549        0.0
1550    };
1551
1552    if is_charging {
1553        if cpu_usage > 25.0 && avg_temp < 75.0 {
1554            set_turbo(true);
1555        } else if avg_temp >= 75.0 {
1556            set_turbo(false);
1557        }
1558    } else {
1559        if cpu_usage > 75.0 {
1560            set_turbo(true);
1561        } else {
1562            set_turbo(false);
1563        }
1564    }
1565
1566    Ok(())
1567}
1568
1569pub fn set_autofreq() -> Result<()> {
1570    let is_charging = charging()?;
1571
1572    let mut cached_sys = CACHED_SYSTEM.lock().unwrap();
1573    let sys = cached_sys.get_refreshed_system();
1574
1575    let cpu_usage: f32 =
1576        sys.cpus().iter().map(|c| c.cpu_usage()).sum::<f32>() / sys.cpus().len() as f32;
1577
1578    let load = System::load_average().one as f32;
1579
1580    let target_governor = get_appropriate_governor(is_charging, cpu_usage, load);
1581    let current_governor = get_current_gov().unwrap_or_else(|_| "unknown".to_string());
1582
1583    if target_governor != current_governor {
1584        set_governor(target_governor)?;
1585    }
1586
1587    set_turbo_based_on_usage(cpu_usage, is_charging)?;
1588
1589    Ok(())
1590}
1591
1592#[cfg(test)]
1593mod tests {
1594    use super::*;
1595
1596    #[test]
1597    fn test_governor_override() {
1598        use std::str::FromStr;
1599        assert_eq!(
1600            GovernorOverride::from_str("powersave").unwrap(),
1601            GovernorOverride::Powersave
1602        );
1603        assert_eq!(
1604            GovernorOverride::from_str("performance").unwrap(),
1605            GovernorOverride::Performance
1606        );
1607        assert_eq!(
1608            GovernorOverride::from_str("invalid").unwrap(),
1609            GovernorOverride::Default
1610        );
1611    }
1612
1613    #[test]
1614    fn test_turbo_override() {
1615        use std::str::FromStr;
1616        assert_eq!(
1617            TurboOverride::from_str("never").unwrap(),
1618            TurboOverride::Never
1619        );
1620        assert_eq!(
1621            TurboOverride::from_str("always").unwrap(),
1622            TurboOverride::Always
1623        );
1624        assert_eq!(
1625            TurboOverride::from_str("auto").unwrap(),
1626            TurboOverride::Auto
1627        );
1628    }
1629
1630    #[test]
1631    fn test_temp_cache() {
1632        let cache = TempSensorCache::new();
1633        let temp = cache.read_core_temp(0);
1634        assert!(temp >= 0.0);
1635    }
1636}