tuned-ppd: Keep track of active and base profile
Instead of querying TuneD each time we want to determine the active TuneD profile, remember its value. Also watch for signals from TuneD, updating the value when the TuneD profile changes (resolves #689). The daemon now also keeps track of the "base" PPD profile, which is restored when all profile holds are released or when tuned-ppd is restarted. For the latter purpose, this profile is also saved in a file. Direct access via two dictionaries (one for AC, one for DC) was clumsy, this commit replaces it with a new class - ProfileMap.
This commit is contained in:
parent
79547f093f
commit
d5bd033422
5 changed files with 100 additions and 51 deletions
1
Makefile
1
Makefile
|
|
@ -178,6 +178,7 @@ install: install-dirs
|
|||
echo -n > $(DESTDIR)$(TUNED_CFG_DIR)/active_profile
|
||||
echo -n > $(DESTDIR)$(TUNED_CFG_DIR)/profile_mode
|
||||
echo -n > $(DESTDIR)$(TUNED_CFG_DIR)/post_loaded_profile
|
||||
echo -n > $(DESTDIR)$(TUNED_CFG_DIR)/ppd_base_profile
|
||||
install -Dpm 0644 bootcmdline $(DESTDIR)$(TUNED_CFG_DIR)/bootcmdline
|
||||
install -Dpm 0644 modules.conf $(DESTDIR)$(SYSCONFDIR)/modprobe.d/tuned.conf
|
||||
|
||||
|
|
|
|||
|
|
@ -504,6 +504,7 @@ fi
|
|||
%config(noreplace) %verify(not size mtime md5) %{_sysconfdir}/tuned/active_profile
|
||||
%config(noreplace) %verify(not size mtime md5) %{_sysconfdir}/tuned/profile_mode
|
||||
%config(noreplace) %verify(not size mtime md5) %{_sysconfdir}/tuned/post_loaded_profile
|
||||
%config(noreplace) %verify(not size mtime md5) %{_sysconfdir}/tuned/ppd_base_profile
|
||||
%config(noreplace) %{_sysconfdir}/tuned/tuned-main.conf
|
||||
%config(noreplace) %verify(not size mtime md5) %{_sysconfdir}/tuned/bootcmdline
|
||||
%verify(not size mtime md5) %{_sysconfdir}/modprobe.d/tuned.conf
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ PPD_DBUS_BUS = PPD_NAMESPACE
|
|||
PPD_DBUS_OBJECT = "/net/hadess/PowerProfiles"
|
||||
PPD_DBUS_INTERFACE = PPD_DBUS_BUS
|
||||
PPD_CONFIG_FILE = "/etc/tuned/ppd.conf"
|
||||
PPD_BASE_PROFILE_FILE = "/etc/tuned/ppd_base_profile"
|
||||
|
||||
# After adding new option to tuned-main.conf add here its name with CFG_ prefix
|
||||
# and eventually default value with CFG_DEF_ prefix (default is None)
|
||||
|
|
|
|||
|
|
@ -12,8 +12,23 @@ DEFAULT_PROFILE_OPTION = "default"
|
|||
BATTERY_DETECTION_OPTION = "battery_detection"
|
||||
|
||||
|
||||
class ProfileMap:
|
||||
def __init__(self, ac_map, dc_map):
|
||||
self._ac_map = ac_map
|
||||
self._dc_map = dc_map
|
||||
|
||||
def get(self, profile, on_battery):
|
||||
profile_map = self._dc_map if on_battery else self._ac_map
|
||||
return profile_map[profile]
|
||||
|
||||
def keys(self, on_battery):
|
||||
profile_map = self._dc_map if on_battery else self._ac_map
|
||||
return profile_map.keys()
|
||||
|
||||
|
||||
class PPDConfig:
|
||||
def __init__(self, config_file):
|
||||
def __init__(self, config_file, tuned_interface):
|
||||
self._tuned_interface = tuned_interface
|
||||
self.load_from_file(config_file)
|
||||
|
||||
@property
|
||||
|
|
@ -32,10 +47,6 @@ class PPDConfig:
|
|||
def tuned_to_ppd(self):
|
||||
return self._tuned_to_ppd
|
||||
|
||||
@property
|
||||
def ppd_to_tuned_battery(self):
|
||||
return self._ppd_to_tuned_battery
|
||||
|
||||
def load_from_file(self, config_file):
|
||||
cfg = ConfigParser()
|
||||
|
||||
|
|
@ -48,38 +59,41 @@ class PPDConfig:
|
|||
|
||||
if PROFILES_SECTION not in cfg:
|
||||
raise TunedException("Missing profiles section in the configuration file '%s'" % config_file)
|
||||
self._ppd_to_tuned = dict(cfg[PROFILES_SECTION])
|
||||
profile_dict_ac = dict(cfg[PROFILES_SECTION])
|
||||
|
||||
if not all(isinstance(mapped_profile, str) for mapped_profile in self._ppd_to_tuned.values()):
|
||||
raise TunedException("Invalid profile mapping in the configuration file '%s'" % config_file)
|
||||
|
||||
if len(set(self._ppd_to_tuned.values())) != len(self._ppd_to_tuned):
|
||||
raise TunedException("Duplicate profile mapping in the configuration file '%s'" % config_file)
|
||||
self._tuned_to_ppd = {v: k for k, v in self._ppd_to_tuned.items()}
|
||||
|
||||
if PPD_POWER_SAVER not in self._ppd_to_tuned:
|
||||
if PPD_POWER_SAVER not in profile_dict_ac:
|
||||
raise TunedException("Missing power-saver profile in the configuration file '%s'" % config_file)
|
||||
|
||||
if PPD_PERFORMANCE not in self._ppd_to_tuned:
|
||||
if PPD_PERFORMANCE not in profile_dict_ac:
|
||||
raise TunedException("Missing performance profile in the configuration file '%s'" % config_file)
|
||||
|
||||
if MAIN_SECTION not in cfg or DEFAULT_PROFILE_OPTION not in cfg[MAIN_SECTION]:
|
||||
raise TunedException("Missing default profile in the configuration file '%s'" % config_file)
|
||||
|
||||
self._default_profile = cfg[MAIN_SECTION][DEFAULT_PROFILE_OPTION]
|
||||
if self._default_profile not in self._ppd_to_tuned:
|
||||
raise TunedException("Unknown default profile '%s'" % self._default_profile)
|
||||
if self._default_profile not in profile_dict_ac:
|
||||
raise TunedException("Default profile '%s' missing in the profile mapping" % self._default_profile)
|
||||
|
||||
if BATTERY_DETECTION_OPTION not in cfg[MAIN_SECTION]:
|
||||
raise TunedException("Missing battery detection option in the configuration file '%s'" % config_file)
|
||||
self._ppd_to_tuned_battery = self._ppd_to_tuned
|
||||
self._battery_detection = cfg.getboolean(MAIN_SECTION, BATTERY_DETECTION_OPTION)
|
||||
if self._battery_detection:
|
||||
if BATTERY_SECTION not in cfg:
|
||||
raise TunedException("Missing battery section in the configuration file '%s'" % config_file)
|
||||
for k, _v in dict(cfg[PROFILES_SECTION]).items():
|
||||
if k in cfg[BATTERY_SECTION].keys():
|
||||
self._tuned_to_ppd = self._tuned_to_ppd | {cfg[BATTERY_SECTION][k]:k}
|
||||
for k, v in dict(cfg[BATTERY_SECTION]).items():
|
||||
if k in cfg[PROFILES_SECTION].keys():
|
||||
self._ppd_to_tuned_battery = self._ppd_to_tuned_battery | {k:v}
|
||||
self._battery_detection = cfg.getboolean(MAIN_SECTION, BATTERY_DETECTION_OPTION, fallback=BATTERY_SECTION in cfg)
|
||||
|
||||
if self._battery_detection and BATTERY_SECTION not in cfg:
|
||||
raise TunedException("Missing battery section in the configuration file '%s'" % config_file)
|
||||
|
||||
profile_dict_dc = profile_dict_ac | dict(cfg[BATTERY_SECTION]) if self._battery_detection else profile_dict_ac
|
||||
|
||||
# Make sure all of the TuneD profiles specified in the configuration file actually exist
|
||||
unknown_tuned_profiles = (set(profile_dict_ac.values()) | set(profile_dict_dc.values())) - set(self._tuned_interface.profiles())
|
||||
if unknown_tuned_profiles:
|
||||
raise TunedException("Unknown TuneD profiles in the configuration file: " + ", ".join(unknown_tuned_profiles))
|
||||
|
||||
# Make sure there are no PPD profiles appearing in the battery section which are not defined before
|
||||
unknown_battery_profiles = set(profile_dict_dc.keys()) - set(profile_dict_ac.keys())
|
||||
if unknown_battery_profiles:
|
||||
raise TunedException("Unknown PPD profiles in the battery section: " + ", ".join(unknown_battery_profiles))
|
||||
|
||||
# Make sure the profile mapping is injective so it can be reverted
|
||||
if len(set(profile_dict_ac.values())) != len(profile_dict_ac) or len(set(profile_dict_dc.values())) != len(profile_dict_dc):
|
||||
raise TunedException("Duplicate profile mapping in the configuration file '%s'" % config_file)
|
||||
|
||||
self._ppd_to_tuned = ProfileMap(profile_dict_ac, profile_dict_dc)
|
||||
self._tuned_to_ppd = ProfileMap({v: k for k, v in profile_dict_ac.items()}, {v: k for k, v in profile_dict_dc.items()})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from tuned import exports, logs
|
||||
from tuned.utils.commands import commands
|
||||
from tuned.consts import PPD_CONFIG_FILE
|
||||
from tuned.consts import PPD_CONFIG_FILE, PPD_BASE_PROFILE_FILE
|
||||
from tuned.ppd.config import PPDConfig, PPD_PERFORMANCE, PPD_POWER_SAVER
|
||||
from enum import StrEnum
|
||||
import threading
|
||||
|
|
@ -105,13 +105,30 @@ class Controller(exports.interfaces.ExportableInterface):
|
|||
self._terminate = threading.Event()
|
||||
self._battery_handler = None
|
||||
self._on_battery = False
|
||||
self._tuned_interface.connect_to_signal("profile_changed", self._tuned_profile_changed)
|
||||
self.initialize()
|
||||
|
||||
def _upower_changed(self, interface, changed, invalidated):
|
||||
self._on_battery = bool(self._upower_properties.Get(UPOWER_DBUS_INTERFACE, "OnBattery"))
|
||||
log.info("Battery status changed: " + ("DC (battery)" if self._on_battery else "AC (charging)"))
|
||||
tuned_profile = self._config.ppd_to_tuned_battery[self._base_profile] if self._on_battery else self._config.ppd_to_tuned[self._base_profile]
|
||||
self.switch_profile(tuned_profile)
|
||||
self.switch_profile(self._active_profile)
|
||||
|
||||
def _tuned_profile_changed(self, tuned_profile, result, errstr):
|
||||
if not result:
|
||||
return
|
||||
self._profile_holds.clear()
|
||||
try:
|
||||
ppd_profile = self._config.tuned_to_ppd.get(tuned_profile, self._on_battery)
|
||||
except KeyError:
|
||||
ppd_profile = UNKNOWN_PROFILE
|
||||
log.warning("TuneD profile changed to an unknown profile '%s'" % tuned_profile)
|
||||
if self._active_profile != ppd_profile:
|
||||
log.info("Profile changed to '%s'" % ppd_profile)
|
||||
self._active_profile = ppd_profile
|
||||
exports.property_changed("ActiveProfile", self._active_profile)
|
||||
if ppd_profile != UNKNOWN_PROFILE:
|
||||
self._base_profile = ppd_profile
|
||||
self._save_base_profile(ppd_profile)
|
||||
|
||||
def _setup_battery_signaling(self):
|
||||
self._on_battery = False
|
||||
|
|
@ -140,14 +157,31 @@ class Controller(exports.interfaces.ExportableInterface):
|
|||
self._performance_degraded = performance_degraded
|
||||
exports.property_changed("PerformanceDegraded", performance_degraded)
|
||||
|
||||
def _load_base_profile(self):
|
||||
return self._cmd.read_file(PPD_BASE_PROFILE_FILE, no_error=True).strip() or None
|
||||
|
||||
def _save_base_profile(self, profile):
|
||||
self._cmd.write_to_file(PPD_BASE_PROFILE_FILE, profile + "\n")
|
||||
|
||||
def _set_tuned_profile(self, tuned_profile):
|
||||
active_tuned_profile = self._tuned_interface.active_profile()
|
||||
if active_tuned_profile == tuned_profile:
|
||||
return True
|
||||
log.info("Setting TuneD profile to '%s'" % tuned_profile)
|
||||
ok, error_msg = self._tuned_interface.switch_profile(tuned_profile)
|
||||
if not ok:
|
||||
log.error(str(error_msg))
|
||||
return bool(ok)
|
||||
|
||||
def initialize(self):
|
||||
self._active_profile = None
|
||||
self._profile_holds = ProfileHoldManager(self)
|
||||
self._performance_degraded = PerformanceDegraded.NONE
|
||||
self._config = PPDConfig(PPD_CONFIG_FILE)
|
||||
self._config = PPDConfig(PPD_CONFIG_FILE, self._tuned_interface)
|
||||
self._setup_battery_signaling()
|
||||
active_profile = self.active_profile()
|
||||
self._base_profile = active_profile if active_profile != UNKNOWN_PROFILE else self._config.default_profile
|
||||
self._base_profile = self._load_base_profile() or self._config.default_profile
|
||||
self.switch_profile(self._base_profile)
|
||||
self._save_base_profile(self._base_profile)
|
||||
|
||||
def run(self):
|
||||
exports.start()
|
||||
|
|
@ -167,16 +201,12 @@ class Controller(exports.interfaces.ExportableInterface):
|
|||
self._terminate.set()
|
||||
|
||||
def switch_profile(self, profile):
|
||||
if self.active_profile() == profile:
|
||||
return
|
||||
tuned_profile = self._config.ppd_to_tuned_battery[profile] if self._on_battery else self._config.ppd_to_tuned[profile]
|
||||
log.info("Switching to profile '%s'" % tuned_profile)
|
||||
self._tuned_interface.switch_profile(tuned_profile)
|
||||
exports.property_changed("ActiveProfile", profile)
|
||||
|
||||
def active_profile(self):
|
||||
tuned_profile = self._tuned_interface.active_profile()
|
||||
return self._config.tuned_to_ppd.get(tuned_profile, UNKNOWN_PROFILE)
|
||||
if not self._set_tuned_profile(self._config.ppd_to_tuned.get(profile, self._on_battery)):
|
||||
return False
|
||||
if self._active_profile != profile:
|
||||
exports.property_changed("ActiveProfile", profile)
|
||||
self._active_profile = profile
|
||||
return True
|
||||
|
||||
@exports.export("sss", "u")
|
||||
def HoldProfile(self, profile, reason, app_id, caller):
|
||||
|
|
@ -198,21 +228,23 @@ class Controller(exports.interfaces.ExportableInterface):
|
|||
|
||||
@exports.property_setter("ActiveProfile")
|
||||
def set_active_profile(self, profile):
|
||||
if profile not in self._config.ppd_to_tuned:
|
||||
if profile not in self._config.ppd_to_tuned.keys(self._on_battery):
|
||||
raise dbus.exceptions.DBusException("Invalid profile '%s'" % profile)
|
||||
log.debug("Setting base profile to %s" % profile)
|
||||
self._base_profile = profile
|
||||
self._profile_holds.clear()
|
||||
self.switch_profile(profile)
|
||||
if not self.switch_profile(profile):
|
||||
raise dbus.exceptions.DBusException("Error setting profile %s'" % profile)
|
||||
self._base_profile = profile
|
||||
self._save_base_profile(profile)
|
||||
|
||||
@exports.property_getter("ActiveProfile")
|
||||
def get_active_profile(self):
|
||||
return self.active_profile()
|
||||
return self._active_profile
|
||||
|
||||
@exports.property_getter("Profiles")
|
||||
def get_profiles(self):
|
||||
return dbus.Array(
|
||||
[{"Profile": profile, "Driver": DRIVER} for profile in self._config.ppd_to_tuned.keys()],
|
||||
[{"Profile": profile, "Driver": DRIVER} for profile in self._config.ppd_to_tuned.keys(self._on_battery)],
|
||||
signature="a{sv}",
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue