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