1use 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
17pub 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
53pub 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
156const 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
205pub 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
255pub 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#[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#[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
498pub 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
534pub 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
545pub 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
612pub 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
674pub 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
695pub 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
745pub 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
813pub 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
857pub 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
893pub 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
1018pub 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
1070pub 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
1145fn 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
1195fn 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
1237fn 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
1275fn 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
1364fn 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
1409fn 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}