scx_utils/
autopower.rs

1use std::fmt;
2use std::fs;
3use std::sync::OnceLock;
4use std::sync::atomic::{AtomicUsize, Ordering};
5
6use zbus::Result;
7use zbus::blocking::Connection;
8use zbus::proxy;
9
10#[proxy(
11    interface = "net.hadess.PowerProfiles",
12    default_service = "net.hadess.PowerProfiles",
13    default_path = "/net/hadess/PowerProfiles"
14)]
15trait PowerProfiles {
16    #[zbus(property)]
17    fn active_profile(&self) -> Result<String>;
18}
19
20static POWER_PROFILES_PROXY: OnceLock<PowerProfilesProxyBlocking<'static>> = OnceLock::new();
21static RETRIES: AtomicUsize = AtomicUsize::new(0);
22const MAX_RETRIES: usize = 10;
23
24#[derive(Debug, Copy, Clone, Eq, PartialEq)]
25pub enum PowerProfile {
26    Powersave,
27    Balanced,
28    Performance,
29    Unknown,
30}
31
32impl fmt::Display for PowerProfile {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            PowerProfile::Powersave => "powersave",
36            PowerProfile::Balanced => "balanced",
37            PowerProfile::Performance => "performance",
38            PowerProfile::Unknown => "unknown",
39        }
40        .fmt(f)
41    }
42}
43
44fn read_energy_profile() -> PowerProfile {
45    let energy_pref_path = "/sys/devices/system/cpu/cpufreq/policy0/energy_performance_preference";
46    let scaling_governor_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_governor";
47
48    fs::read_to_string(energy_pref_path)
49        .ok()
50        .or_else(|| fs::read_to_string(scaling_governor_path).ok())
51        .map(|s| match s.trim_end() {
52            "power" | "balance_power" | "powersave" => PowerProfile::Powersave,
53            "balance_performance" => PowerProfile::Balanced,
54            "performance" => PowerProfile::Performance,
55            _ => PowerProfile::Unknown,
56        })
57        .unwrap_or(PowerProfile::Unknown)
58}
59
60pub fn fetch_power_profile(no_ppd: bool) -> PowerProfile {
61    fn parse_profile(profile: &str) -> PowerProfile {
62        match profile {
63            "power-saver" => PowerProfile::Powersave,
64            "balanced" => PowerProfile::Balanced,
65            "performance" => PowerProfile::Performance,
66            _ => PowerProfile::Unknown,
67        }
68    }
69
70    if no_ppd {
71        return read_energy_profile();
72    }
73    let proxy = POWER_PROFILES_PROXY.get();
74    if let Some(proxy) = proxy {
75        proxy.active_profile().map_or_else(
76            |e| {
77                log::debug!("failed to fetch the active power profile from ppd: {e}");
78                read_energy_profile()
79            },
80            |profile| parse_profile(&profile),
81        )
82    } else {
83        let retries = RETRIES.fetch_add(1, Ordering::Relaxed);
84        if retries < MAX_RETRIES {
85            let proxy = Connection::system().map(Box::new).map(Box::leak).map(|bus|
86                    // This cannot fail. Proxy::new() does not check the existence of interface
87                    PowerProfilesProxyBlocking::new(bus).unwrap());
88            match proxy {
89                Ok(proxy) => match proxy.active_profile() {
90                    Ok(profile) => {
91                        let _ = POWER_PROFILES_PROXY.set(proxy);
92                        parse_profile(&profile)
93                    }
94                    Err(e) => {
95                        log::debug!("failed to communicate with ppd (retry {retries}): {e}");
96                        read_energy_profile()
97                    }
98                },
99                Err(e) => {
100                    log::debug!("failed to communicate with dbus (retry {retries}): {e}");
101                    read_energy_profile()
102                }
103            }
104        } else {
105            read_energy_profile()
106        }
107    }
108}