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