Skip to content

Commit

Permalink
tuned-ppd: Add docstrings
Browse files Browse the repository at this point in the history
  • Loading branch information
zacikpa committed Aug 9, 2024
1 parent d493ad7 commit 699c5a2
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 2 deletions.
25 changes: 25 additions & 0 deletions tuned/ppd/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,62 @@


class ProfileMap:
"""
Mapping of PPD profiles to TuneD profiles.
"""
def __init__(self, ac_map, dc_map):
self._ac_map = ac_map
self._dc_map = dc_map

def get(self, profile, on_battery):
"""
Returns a TuneD profile corresponding to the given
PPD profile and power supply status.
"""
if on_battery and profile in self._dc_map:
return self._dc_map[profile]
return self._ac_map[profile]

def keys(self):
"""
Returns the supported PPD keys.
"""
return self._ac_map.keys()


class PPDConfig:
"""
Configuration for the tuned-ppd daemon.
"""
def __init__(self, config_file, tuned_interface):
self._tuned_interface = tuned_interface
self.load_from_file(config_file)

@property
def battery_detection(self):
"""
Whether battery detection is enabled.
"""
return self._battery_detection

@property
def default_profile(self):
"""
Default PPD profile to set during initialization.
"""
return self._default_profile

@property
def ppd_to_tuned(self):
"""
Mapping of PPD profiles to TuneD profiles.
"""
return self._ppd_to_tuned

def load_from_file(self, config_file):
"""
Loads the configuration from the provided file.
"""
cfg = ConfigParser()

if not os.path.isfile(config_file):
Expand Down
119 changes: 117 additions & 2 deletions tuned/ppd/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,29 @@
UPOWER_DBUS_INTERFACE = "org.freedesktop.UPower"

class PerformanceDegraded(StrEnum):
"""
Possible reasons for performance degradation.
"""
NONE = ""
LAP_DETECTED = "lap-detected"
HIGH_OPERATING_TEMPERATURE = "high-operating-temperature"


class ProfileHold(object):
"""
Class holding information about a single profile hold,
i.e., a temporary profile switch requested by a process.
"""
def __init__(self, profile, reason, app_id, watch):
self.profile = profile
self.reason = reason
self.app_id = app_id
self.watch = watch

def as_dict(self):
"""
Returns the hold information as a Python dictionary.
"""
return {
"Profile": self.profile,
"Reason": self.reason,
Expand All @@ -40,12 +50,21 @@ def as_dict(self):


class ProfileHoldManager(object):
"""
Manager of profile holds responsible for their creation/deletion
and for choosing the effective one. Holds are identified using
integer cookies which are distributed to the hold-requesting processes.
"""
def __init__(self, controller):
self._holds = {}
self._cookie_counter = 0
self._controller = controller

def _callback(self, cookie, app_id):
def _removal_callback(self, cookie, app_id):
"""
Returns the callback to invoke when the process with the given ID
(which requested a hold with the given cookie) disappears.
"""
def callback(name):
if name == "":
log.info("Application '%s' disappeared, releasing hold '%s'" % (app_id, cookie))
Expand All @@ -54,11 +73,17 @@ def callback(name):
return callback

def _effective_hold_profile(self):
"""
Returns the hold to use from the set of all active ones.
"""
if any(hold.profile == PPD_POWER_SAVER for hold in self._holds.values()):
return PPD_POWER_SAVER
return PPD_PERFORMANCE

def _cancel(self, cookie):
"""
Cancels the hold saved under the provided cookie.
"""
if cookie not in self._holds:
return
hold = self._holds.pop(cookie)
Expand All @@ -68,22 +93,35 @@ def _cancel(self, cookie):
log.info("Releasing hold '%s': profile '%s' by application '%s'" % (cookie, hold.profile, hold.app_id))

def as_dbus_array(self):
"""
Returns the information about current holds as a DBus-compatible array.
"""
return dbus.Array([hold.as_dict() for hold in self._holds.values()], signature="a{sv}")

def add(self, profile, reason, app_id, caller):
"""
Adds a new profile hold.
"""
cookie = self._cookie_counter
self._cookie_counter += 1
watch = self._controller.bus.watch_name_owner(caller, self._callback(cookie, app_id))
watch = self._controller.bus.watch_name_owner(caller, self._removal_callback(cookie, app_id))
log.info("Adding hold '%s': profile '%s' by application '%s'" % (cookie, profile, app_id))
self._holds[cookie] = ProfileHold(profile, reason, app_id, watch)
exports.property_changed("ActiveProfileHolds", self.as_dbus_array())
self._controller.switch_profile(self._effective_hold_profile())
return cookie

def has(self, cookie):
"""
Returns True if there is a hold under the given cookie.
"""
return cookie in self._holds

def remove(self, cookie):
"""
Releases the hold saved under the provided cookie and
sets the next profile.
"""
self._cancel(cookie)
if len(self._holds) != 0:
new_profile = self._effective_hold_profile()
Expand All @@ -92,11 +130,17 @@ def remove(self, cookie):
self._controller.switch_profile(new_profile)

def clear(self):
"""
Releases all profile holds.
"""
for cookie in list(self._holds.keys()):
self._cancel(cookie)


class Controller(exports.interfaces.ExportableInterface):
"""
The main tuned-ppd controller, exporting its DBus interface.
"""
def __init__(self, bus, tuned_interface):
super(Controller, self).__init__()
self._bus = bus
Expand All @@ -106,12 +150,18 @@ def __init__(self, bus, tuned_interface):
self.initialize()

def upower_changed(self, interface, changed, invalidated):
"""
The callback to invoke when the power supply changes.
"""
properties = dbus.Interface(self.proxy, dbus.PROPERTIES_IFACE)
self._on_battery = bool(properties.Get(UPOWER_DBUS_INTERFACE, "OnBattery"))
log.info("Battery status: " + ("DC (battery)" if self._on_battery else "AC (charging)"))
self.switch_profile(self._active_profile)

def setup_battery_signaling(self):
"""
Sets up handling of power supply changes.
"""
try:
bus = dbus.SystemBus()
self.proxy = bus.get_object(UPOWER_DBUS_NAME, UPOWER_DBUS_PATH)
Expand All @@ -121,6 +171,9 @@ def setup_battery_signaling(self):
log.debug(error)

def _check_performance_degraded(self):
"""
Checks the current performance degradation status and sends a signal if it changed.
"""
performance_degraded = PerformanceDegraded.NONE
if os.path.exists(NO_TURBO_PATH) and self._cmd.read_file(NO_TURBO_PATH).strip() == "1":
performance_degraded = PerformanceDegraded.HIGH_OPERATING_TEMPERATURE
Expand All @@ -132,12 +185,21 @@ def _check_performance_degraded(self):
exports.property_changed("PerformanceDegraded", performance_degraded)

def _load_base_profile(self):
"""
Loads and returns the saved PPD base profile.
"""
return self._cmd.read_file(PPD_BASE_PROFILE_FILE, no_error=True).strip() or None

def _save_base_profile(self, profile):
"""
Saves the given PPD profile into the base profile file.
"""
self._cmd.write_to_file(PPD_BASE_PROFILE_FILE, profile + "\n")

def _set_tuned_profile(self, tuned_profile):
"""
Sets the TuneD profile to the given one if not already set.
"""
active_tuned_profile = self._tuned_interface.active_profile()
if active_tuned_profile == tuned_profile:
return True
Expand All @@ -148,6 +210,9 @@ def _set_tuned_profile(self, tuned_profile):
return bool(ok)

def initialize(self):
"""
Initializes the controller.
"""
self._active_profile = None
self._profile_holds = ProfileHoldManager(self)
self._performance_degraded = PerformanceDegraded.NONE
Expand All @@ -160,23 +225,41 @@ def initialize(self):
self.setup_battery_signaling()

def run(self):
"""
Exports the DBus interface and runs the main daemon loop.
"""
exports.start()
while not self._cmd.wait(self._terminate, 1):
self._check_performance_degraded()
exports.stop()

@property
def bus(self):
"""
DBus interface for communication with other services.
"""
return self._bus

@property
def base_profile(self):
"""
The base PPD profile. This is the profile to restore when
all profile holds are released or when tuned-ppd is restarted.
It may not be equal to the currently active profile.
"""
return self._base_profile

def terminate(self):
"""
Stops the main loop of the daemon.
"""
self._terminate.set()

def switch_profile(self, profile):
"""
Sets the currently active profile to the given one, if not already set.
Does not change the base profile.
"""
if not self._set_tuned_profile(self._config.ppd_to_tuned.get(profile, self._on_battery)):
return False
if self._active_profile != profile:
Expand All @@ -185,6 +268,10 @@ def switch_profile(self, profile):
return True

def _check_active_profile(self, err_ret=UNKNOWN_PROFILE):
"""
Checks that the currently active TuneD profile corresponds to the currently active
PPD profile. If yes, returns the PPD profile, otherwise returns err_ret.
"""
active_tuned_profile = self._tuned_interface.active_profile()
expected_tuned_profile = self._config.ppd_to_tuned.get(self._active_profile, self._on_battery)
if active_tuned_profile != expected_tuned_profile:
Expand All @@ -196,6 +283,9 @@ def _check_active_profile(self, err_ret=UNKNOWN_PROFILE):

@exports.export("sss", "u")
def HoldProfile(self, profile, reason, app_id, caller):
"""
Initiates a profile hold and returns a cookie for referring to it.
"""
if profile != PPD_POWER_SAVER and profile != PPD_PERFORMANCE:
raise dbus.exceptions.DBusException(
"Only '%s' and '%s' profiles may be held" % (PPD_POWER_SAVER, PPD_PERFORMANCE)
Expand All @@ -204,16 +294,26 @@ def HoldProfile(self, profile, reason, app_id, caller):

@exports.export("u", "")
def ReleaseProfile(self, cookie, caller):
"""
Releases a held profile with the given cookie.
"""
if not self._profile_holds.has(cookie):
raise dbus.exceptions.DBusException("No active hold for cookie '%s'" % cookie)
self._profile_holds.remove(cookie)

@exports.signal("u")
def ProfileReleased(self, cookie):
"""
The DBus signal sent when a held profile is released.
"""
pass

@exports.property_setter("ActiveProfile")
def set_active_profile(self, profile):
"""
Sets the base profile to the given one and also makes it active.
If there are any active profile holds, these are cancelled.
"""
if profile not in self._config.ppd_to_tuned.keys():
raise dbus.exceptions.DBusException("Invalid profile '%s'" % profile)
log.debug("Setting base profile to %s" % profile)
Expand All @@ -225,23 +325,38 @@ def set_active_profile(self, profile):

@exports.property_getter("ActiveProfile")
def get_active_profile(self):
"""
Returns the currently active PPD profile.
"""
return self._check_active_profile()

@exports.property_getter("Profiles")
def get_profiles(self):
"""
Returns a DBus array of all available PPD profiles.
"""
return dbus.Array(
[{"Profile": profile, "Driver": DRIVER} for profile in self._config.ppd_to_tuned.keys()],
signature="a{sv}",
)

@exports.property_getter("Actions")
def get_actions(self):
"""
Returns a DBus array of all available actions (currently there are none).
"""
return dbus.Array([], signature="s")

@exports.property_getter("PerformanceDegraded")
def get_performance_degraded(self):
"""
Returns the current performance degradation status.
"""
return self._performance_degraded

@exports.property_getter("ActiveProfileHolds")
def get_active_profile_holds(self):
"""
Returns a DBus array of active profile holds.
"""
return self._profile_holds.as_dbus_array()

0 comments on commit 699c5a2

Please sign in to comment.