scx_utils/
autopower.rs

1use std::fmt;
2use std::fs;
3use std::sync::atomic::{AtomicUsize, Ordering};
4use std::sync::OnceLock;
5
6use zbus::blocking::Connection;
7use zbus::proxy;
8use zbus::Result;
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 { power: bool },
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 { power: true } => "balanced_power",
37            PowerProfile::Balanced { power: false } => "balanced_performance",
38            PowerProfile::Performance => "performance",
39            PowerProfile::Unknown => "unknown",
40        }
41        .fmt(f)
42    }
43}
44
45fn read_energy_profile() -> PowerProfile {
46    let energy_pref_path = "/sys/devices/system/cpu/cpufreq/policy0/energy_performance_preference";
47    let scaling_governor_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_governor";
48
49    fs::read_to_string(energy_pref_path)
50        .ok()
51        .or_else(|| fs::read_to_string(scaling_governor_path).ok())
52        .map(|s| match s.trim_end() {
53            "power" | "powersave" => PowerProfile::Powersave,
54            "balance_power" => PowerProfile::Balanced { power: true },
55            "balance_performance" => PowerProfile::Balanced { power: false },
56            "performance" => PowerProfile::Performance,
57            _ => PowerProfile::Unknown,
58        })
59        .unwrap_or(PowerProfile::Unknown)
60}
61
62pub fn fetch_power_profile(no_ppd: bool) -> PowerProfile {
63    fn parse_profile(profile: &str) -> PowerProfile {
64        match profile {
65            "power-saver" => PowerProfile::Powersave,
66            "balanced" => PowerProfile::Balanced { power: false },
67            "performance" => PowerProfile::Performance,
68            _ => PowerProfile::Unknown,
69        }
70    }
71
72    if no_ppd {
73        return read_energy_profile();
74    }
75    let proxy = POWER_PROFILES_PROXY.get();
76    if let Some(proxy) = proxy {
77        proxy.active_profile().map_or_else(
78            |e| {
79                log::debug!("failed to fetch the active power profile from ppd: {e}");
80                read_energy_profile()
81            },
82            |profile| parse_profile(&profile),
83        )
84    } else {
85        let retries = RETRIES.fetch_add(1, Ordering::Relaxed);
86        if retries < MAX_RETRIES {
87            let proxy = Connection::system().map(Box::new).map(Box::leak).map(|bus|
88                    // This cannot fail. Proxy::new() does not check the existence of interface
89                    PowerProfilesProxyBlocking::new(bus).unwrap());
90            match proxy {
91                Ok(proxy) => match proxy.active_profile() {
92                    Ok(profile) => {
93                        let _ = POWER_PROFILES_PROXY.set(proxy);
94                        parse_profile(&profile)
95                    }
96                    Err(e) => {
97                        log::debug!("failed to communicate with ppd (retry {retries}): {e}");
98                        read_energy_profile()
99                    }
100                },
101                Err(e) => {
102                    log::debug!("failed to communicate with dbus (retry {retries}): {e}");
103                    read_energy_profile()
104                }
105            }
106        } else {
107            read_energy_profile()
108        }
109    }
110}