Merge pull request #579 from zacikpa/ppd-daemon
PPD-to-TuneD API translation daemon
This commit is contained in:
commit
11c6c57087
17 changed files with 528 additions and 5 deletions
8
Makefile
8
Makefile
|
|
@ -223,6 +223,14 @@ install: install-dirs
|
|||
install -dD $(DESTDIR)$(DATADIR)/applications
|
||||
desktop-file-install --dir=$(DESTDIR)$(DATADIR)/applications tuned-gui.desktop
|
||||
|
||||
install-ppd: install
|
||||
$(call install_python_script,tuned-ppd.py,$(DESTDIR)/usr/sbin/tuned-ppd)
|
||||
install -Dpm 0644 tuned/ppd/tuned-ppd.service $(DESTDIR)$(UNITDIR)/tuned-ppd.service
|
||||
install -Dpm 0644 tuned/ppd/tuned-ppd.dbus.service $(DESTDIR)$(DATADIR)/dbus-1/system-services/net.hadess.PowerProfiles.service
|
||||
install -Dpm 0644 tuned/ppd/dbus.conf $(DESTDIR)$(DATADIR)/dbus-1/system.d/net.hadess.PowerProfiles.conf
|
||||
install -Dpm 0644 tuned/ppd/tuned-ppd.policy $(DESTDIR)$(DATADIR)/polkit-1/actions/net.hadess.PowerProfiles.policy
|
||||
install -Dpm 0644 tuned/ppd/ppd.conf $(DESTDIR)$(SYSCONFDIR)/tuned/ppd.conf
|
||||
|
||||
clean: clean-html
|
||||
find -name "*.pyc" | xargs rm -f
|
||||
rm -rf $(VERSIONED_NAME) rpm-build-dir
|
||||
|
|
|
|||
44
tuned-ppd.py
Executable file
44
tuned-ppd.py
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/python3 -Es
|
||||
import sys
|
||||
import os
|
||||
import dbus
|
||||
import signal
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
from tuned import exports
|
||||
from tuned.ppd import controller
|
||||
import tuned.consts as consts
|
||||
|
||||
|
||||
def handle_signal(signal_number, handler):
|
||||
def handler_wrapper(_signal_number, _frame):
|
||||
if signal_number == _signal_number:
|
||||
handler()
|
||||
signal.signal(signal_number, handler_wrapper)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if os.geteuid() != 0:
|
||||
print("Superuser permissions are required to run the daemon.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
DBusGMainLoop(set_as_default=True)
|
||||
bus = dbus.SystemBus()
|
||||
try:
|
||||
tuned_object = bus.get_object(consts.DBUS_BUS, consts.DBUS_OBJECT)
|
||||
except dbus.exceptions.DBusException:
|
||||
print("TuneD not found on the DBus, ensure that it is running.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
tuned_iface = dbus.Interface(tuned_object, consts.DBUS_INTERFACE)
|
||||
|
||||
controller = controller.Controller(bus, tuned_iface)
|
||||
|
||||
handle_signal(signal.SIGINT, controller.terminate)
|
||||
handle_signal(signal.SIGTERM, controller.terminate)
|
||||
handle_signal(signal.SIGHUP, controller.load_config)
|
||||
|
||||
dbus_exporter = exports.dbus_with_properties.DBusExporterWithProperties(
|
||||
consts.PPD_DBUS_BUS, consts.PPD_DBUS_INTERFACE, consts.PPD_DBUS_OBJECT, consts.PPD_NAMESPACE
|
||||
)
|
||||
|
||||
exports.register_exporter(dbus_exporter)
|
||||
exports.register_object(controller)
|
||||
controller.run()
|
||||
2
tuned.py
2
tuned.py
|
|
@ -79,7 +79,7 @@ if __name__ == "__main__":
|
|||
args.no_socket = True
|
||||
|
||||
if not args.no_dbus:
|
||||
app.attach_to_dbus(consts.DBUS_BUS, consts.DBUS_OBJECT, consts.DBUS_INTERFACE)
|
||||
app.attach_to_dbus(consts.DBUS_BUS, consts.DBUS_OBJECT, consts.DBUS_INTERFACE, consts.NAMESPACE)
|
||||
|
||||
if not args.no_socket:
|
||||
app.attach_to_unix_socket()
|
||||
|
|
|
|||
20
tuned.spec
20
tuned.spec
|
|
@ -264,6 +264,17 @@ Requires: %{name} = %{version}
|
|||
%description profiles-openshift
|
||||
Additional TuneD profile(s) optimized for OpenShift.
|
||||
|
||||
%package ppd
|
||||
Summary: PPD compatibility daemon
|
||||
Requires: %{name} = %{version}
|
||||
# The compatibility daemon is swappable for power-profiles-daemon
|
||||
Provides: ppd-service
|
||||
Conflicts: ppd-service
|
||||
|
||||
%description ppd
|
||||
An API translation daemon that allows applications to easily transition
|
||||
to TuneD from power-profiles-daemon (PPD).
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n %{name}-%{version}%{?prerel2}
|
||||
|
||||
|
|
@ -276,6 +287,7 @@ make html %{make_python_arg}
|
|||
|
||||
%install
|
||||
make install DESTDIR=%{buildroot} DOCDIR=%{docdir} %{make_python_arg}
|
||||
make install-ppd DESTDIR=%{buildroot} DOCDIR=%{docdir} %{make_python_arg}
|
||||
%if 0%{?rhel}
|
||||
sed -i 's/\(dynamic_tuning[ \t]*=[ \t]*\).*/\10/' %{buildroot}%{_sysconfdir}/tuned/tuned-main.conf
|
||||
%endif
|
||||
|
|
@ -556,6 +568,14 @@ fi
|
|||
%{_prefix}/lib/tuned/openshift-node
|
||||
%{_mandir}/man7/tuned-profiles-openshift.7*
|
||||
|
||||
%files ppd
|
||||
%{_sbindir}/tuned-ppd
|
||||
%{_unitdir}/tuned-ppd.service
|
||||
%{_datadir}/dbus-1/system-services/net.hadess.PowerProfiles.service
|
||||
%{_datadir}/dbus-1/system.d/net.hadess.PowerProfiles.conf
|
||||
%{_datadir}/polkit-1/actions/net.hadess.PowerProfiles.policy
|
||||
%config(noreplace) %{_sysconfdir}/tuned/ppd.conf
|
||||
|
||||
%changelog
|
||||
* Tue Aug 29 2023 Jaroslav Škarvada <jskarvad@redhat.com> - 2.21.0-1
|
||||
- new release
|
||||
|
|
|
|||
|
|
@ -90,6 +90,13 @@ ROLLBACK_FULL = 2
|
|||
PREFIX_PROFILE_FACTORY = "System"
|
||||
PREFIX_PROFILE_USER = "User"
|
||||
|
||||
# PPD-to-tuned API translation daemon configuration
|
||||
PPD_NAMESPACE = "net.hadess.PowerProfiles"
|
||||
PPD_DBUS_BUS = PPD_NAMESPACE
|
||||
PPD_DBUS_OBJECT = "/net/hadess/PowerProfiles"
|
||||
PPD_DBUS_INTERFACE = PPD_DBUS_BUS
|
||||
PPD_CONFIG_FILE = "/etc/tuned/ppd.conf"
|
||||
|
||||
# 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)
|
||||
# and function for check with CFG_FUNC_ prefix
|
||||
|
|
|
|||
|
|
@ -71,11 +71,11 @@ class Application(object):
|
|||
self._handle_signal(signal.SIGINT, self._controller.terminate)
|
||||
self._handle_signal(signal.SIGTERM, self._controller.terminate)
|
||||
|
||||
def attach_to_dbus(self, bus_name, object_name, interface_name):
|
||||
def attach_to_dbus(self, bus_name, object_name, interface_name, namespace):
|
||||
if self._dbus_exporter is not None:
|
||||
raise TunedException("DBus interface is already initialized.")
|
||||
|
||||
self._dbus_exporter = exports.dbus.DBusExporter(bus_name, interface_name, object_name)
|
||||
self._dbus_exporter = exports.dbus.DBusExporter(bus_name, interface_name, object_name, namespace)
|
||||
exports.register_exporter(self._dbus_exporter)
|
||||
|
||||
def attach_to_unix_socket(self):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from . import interfaces
|
||||
from . import controller
|
||||
from . import dbus_exporter as dbus
|
||||
from . import dbus_exporter_with_properties as dbus_with_properties
|
||||
from . import unix_socket_exporter as unix_socket
|
||||
|
||||
def export(*args, **kwargs):
|
||||
|
|
@ -17,6 +18,24 @@ def signal(*args, **kwargs):
|
|||
return method
|
||||
return wrapper
|
||||
|
||||
def property_setter(*args, **kwargs):
|
||||
"""Decorator, use to mark setters of exportable properties."""
|
||||
def wrapper(method):
|
||||
method.property_set_params = [ args, kwargs ]
|
||||
return method
|
||||
return wrapper
|
||||
|
||||
def property_getter(*args, **kwargs):
|
||||
"""Decorator, use to mark getters of exportable properties."""
|
||||
def wrapper(method):
|
||||
method.property_get_params = [ args, kwargs ]
|
||||
return method
|
||||
return wrapper
|
||||
|
||||
def property_changed(*args, **kwargs):
|
||||
ctl = controller.ExportsController.get_instance()
|
||||
return ctl.property_changed(*args, **kwargs)
|
||||
|
||||
def register_exporter(instance):
|
||||
if not isinstance(instance, interfaces.ExporterInterface):
|
||||
raise Exception()
|
||||
|
|
|
|||
|
|
@ -29,6 +29,14 @@ class ExportsController(tuned.patterns.Singleton):
|
|||
"""Check if method was marked with @exports.signal wrapper."""
|
||||
return inspect.ismethod(method) and hasattr(method, "signal_params")
|
||||
|
||||
def _is_exportable_getter(self, method):
|
||||
"""Check if method was marked with @exports.get_property wrapper."""
|
||||
return inspect.ismethod(method) and hasattr(method, "property_get_params")
|
||||
|
||||
def _is_exportable_setter(self, method):
|
||||
"""Check if method was marked with @exports.set_property wrapper."""
|
||||
return inspect.ismethod(method) and hasattr(method, "property_set_params")
|
||||
|
||||
def _export_method(self, method):
|
||||
"""Register method to all exporters."""
|
||||
for exporter in self._exporters:
|
||||
|
|
@ -43,11 +51,29 @@ class ExportsController(tuned.patterns.Singleton):
|
|||
kwargs = method.signal_params[1]
|
||||
exporter.signal(method, *args, **kwargs)
|
||||
|
||||
def _export_getter(self, method):
|
||||
"""Register property getter to all exporters."""
|
||||
for exporter in self._exporters:
|
||||
args = method.property_get_params[0]
|
||||
kwargs = method.property_get_params[1]
|
||||
exporter.property_getter(method, *args, **kwargs)
|
||||
|
||||
def _export_setter(self, method):
|
||||
"""Register property setter to all exporters."""
|
||||
for exporter in self._exporters:
|
||||
args = method.property_set_params[0]
|
||||
kwargs = method.property_set_params[1]
|
||||
exporter.property_setter(method, *args, **kwargs)
|
||||
|
||||
def send_signal(self, signal, *args, **kwargs):
|
||||
"""Register signal to all exporters."""
|
||||
for exporter in self._exporters:
|
||||
exporter.send_signal(signal, *args, **kwargs)
|
||||
|
||||
def property_changed(self, *args, **kwargs):
|
||||
for exporter in self._exporters:
|
||||
exporter.property_changed(*args, **kwargs)
|
||||
|
||||
def period_check(self):
|
||||
"""Allows to perform checks on exporters without special thread."""
|
||||
for exporter in self._exporters:
|
||||
|
|
@ -62,6 +88,10 @@ class ExportsController(tuned.patterns.Singleton):
|
|||
self._export_method(method)
|
||||
for name, method in inspect.getmembers(instance, self._is_exportable_signal):
|
||||
self._export_signal(method)
|
||||
for name, method in inspect.getmembers(instance, self._is_exportable_getter):
|
||||
self._export_getter(method)
|
||||
for name, method in inspect.getmembers(instance, self._is_exportable_setter):
|
||||
self._export_setter(method)
|
||||
|
||||
self._exports_initialized = True
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class DBusExporter(interfaces.ExporterInterface):
|
|||
to an object we dynamically construct.
|
||||
"""
|
||||
|
||||
def __init__(self, bus_name, interface_name, object_name):
|
||||
def __init__(self, bus_name, interface_name, object_name, namespace):
|
||||
# Monkey patching of the D-Bus library _method_reply_error() to reply
|
||||
# tracebacks via D-Bus only if in the debug mode. It doesn't seem there is a
|
||||
# more simple way how to cover all possible exceptions that could occur in
|
||||
|
|
@ -82,6 +82,7 @@ class DBusExporter(interfaces.ExporterInterface):
|
|||
self._bus_name = bus_name
|
||||
self._interface_name = interface_name
|
||||
self._object_name = object_name
|
||||
self._namespace = namespace
|
||||
self._thread = None
|
||||
self._bus_object = None
|
||||
self._polkit = polkit()
|
||||
|
|
@ -130,7 +131,7 @@ class DBusExporter(interfaces.ExporterInterface):
|
|||
raise Exception("Method with this name is already exported.")
|
||||
|
||||
def wrapper(owner, *args, **kwargs):
|
||||
action_id = consts.NAMESPACE + "." + method.__name__
|
||||
action_id = self._namespace + "." + method.__name__
|
||||
caller = args[-1]
|
||||
log.debug("checking authorization for action '%s' requested by caller '%s'" % (action_id, caller))
|
||||
ret = self._polkit.check_authorization(caller, action_id)
|
||||
|
|
|
|||
60
tuned/exports/dbus_exporter_with_properties.py
Normal file
60
tuned/exports/dbus_exporter_with_properties.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
from inspect import ismethod
|
||||
from dbus.service import method, signal
|
||||
from dbus import PROPERTIES_IFACE
|
||||
from dbus.exceptions import DBusException
|
||||
from tuned.exports.dbus_exporter import DBusExporter
|
||||
|
||||
|
||||
class DBusExporterWithProperties(DBusExporter):
|
||||
def __init__(self, bus_name, interface_name, object_name, namespace):
|
||||
super(DBusExporterWithProperties, self).__init__(bus_name, interface_name, object_name, namespace)
|
||||
self._property_setters = {}
|
||||
self._property_getters = {}
|
||||
|
||||
def Get(_, interface_name, property_name):
|
||||
if interface_name != self._interface_name:
|
||||
raise DBusException("Unknown interface: %s" % interface_name)
|
||||
if property_name not in self._property_getters:
|
||||
raise DBusException("No such property: %s" % property_name)
|
||||
getter = self._property_getters[property_name]
|
||||
return getter()
|
||||
|
||||
def Set(_, interface_name, property_name, value):
|
||||
if interface_name != self._interface_name:
|
||||
raise DBusException("Unknown interface: %s" % interface_name)
|
||||
if property_name not in self._property_setters:
|
||||
raise DBusException("No such property: %s" % property_name)
|
||||
setter = self._property_setters[property_name]
|
||||
setter(value)
|
||||
|
||||
def GetAll(_, interface_name):
|
||||
if interface_name != self._interface_name:
|
||||
raise DBusException("Unknown interface: %s" % interface_name)
|
||||
return {name: getter() for name, getter in self._property_getters.items()}
|
||||
|
||||
def PropertiesChanged(_, interface_name, changed_properties, invalidated_properties):
|
||||
if interface_name != self._interface_name:
|
||||
raise DBusException("Unknown interface: %s" % interface_name)
|
||||
|
||||
self._dbus_methods["Get"] = method(PROPERTIES_IFACE, in_signature="ss", out_signature="v")(Get)
|
||||
self._dbus_methods["Set"] = method(PROPERTIES_IFACE, in_signature="ssv")(Set)
|
||||
self._dbus_methods["GetAll"] = method(PROPERTIES_IFACE, in_signature="s", out_signature="a{sv}")(GetAll)
|
||||
self._dbus_methods["PropertiesChanged"] = signal(PROPERTIES_IFACE, signature="sa{sv}as")(PropertiesChanged)
|
||||
self._signals.add("PropertiesChanged")
|
||||
|
||||
def property_changed(self, property_name, value):
|
||||
self.send_signal("PropertiesChanged", self._interface_name, {property_name: value}, {})
|
||||
|
||||
def property_getter(self, method, property_name):
|
||||
if not ismethod(method):
|
||||
raise Exception("Only bound methods can be exported.")
|
||||
if property_name in self._property_getters:
|
||||
raise Exception("A getter for this property is already registered.")
|
||||
self._property_getters[property_name] = method
|
||||
|
||||
def property_setter(self, method, property_name):
|
||||
if not ismethod(method):
|
||||
raise Exception("Only bound methods can be exported.")
|
||||
if property_name in self._property_setters:
|
||||
raise Exception("A setter for this property is already registered.")
|
||||
self._property_setters[property_name] = method
|
||||
61
tuned/ppd/config.py
Normal file
61
tuned/ppd/config.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
from tuned.utils.config_parser import ConfigParser, Error
|
||||
from tuned.exceptions import TunedException
|
||||
import os
|
||||
|
||||
PPD_POWER_SAVER = "power-saver"
|
||||
PPD_PERFORMANCE = "performance"
|
||||
|
||||
MAIN_SECTION = "main"
|
||||
PROFILES_SECTION = "profiles"
|
||||
DEFAULT_PROFILE_OPTION = "default"
|
||||
|
||||
|
||||
class PPDConfig:
|
||||
def __init__(self, config_file):
|
||||
self.load_from_file(config_file)
|
||||
|
||||
@property
|
||||
def default_profile(self):
|
||||
return self._default_profile
|
||||
|
||||
@property
|
||||
def ppd_to_tuned(self):
|
||||
return self._ppd_to_tuned
|
||||
|
||||
@property
|
||||
def tuned_to_ppd(self):
|
||||
return self._tuned_to_ppd
|
||||
|
||||
def load_from_file(self, config_file):
|
||||
cfg = ConfigParser()
|
||||
|
||||
if not os.path.isfile(config_file):
|
||||
raise TunedException("Configuration file '%s' does not exist" % config_file)
|
||||
try:
|
||||
cfg.read(config_file)
|
||||
except Error:
|
||||
raise TunedException("Error parsing the configuration file '%s'" % config_file)
|
||||
|
||||
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])
|
||||
|
||||
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:
|
||||
raise TunedException("Missing power-saver profile in the configuration file '%s'" % config_file)
|
||||
|
||||
if PPD_PERFORMANCE not in self._ppd_to_tuned:
|
||||
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)
|
||||
200
tuned/ppd/controller.py
Normal file
200
tuned/ppd/controller.py
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
from tuned import exports, logs
|
||||
from tuned.utils.commands import commands
|
||||
from tuned.consts import PPD_CONFIG_FILE
|
||||
from tuned.ppd.config import PPDConfig, PPD_PERFORMANCE, PPD_POWER_SAVER
|
||||
from enum import StrEnum
|
||||
import threading
|
||||
import dbus
|
||||
import os
|
||||
|
||||
log = logs.get()
|
||||
|
||||
DRIVER = "tuned"
|
||||
NO_TURBO_PATH = "/sys/devices/system/cpu/intel_pstate/no_turbo"
|
||||
LAP_MODE_PATH = "/sys/bus/platform/devices/thinkpad_acpi/dytc_lapmode"
|
||||
|
||||
|
||||
class PerformanceDegraded(StrEnum):
|
||||
NONE = ""
|
||||
LAP_DETECTED = "lap-detected"
|
||||
HIGH_OPERATING_TEMPERATURE = "high-operating-temperature"
|
||||
|
||||
|
||||
class ProfileHold(object):
|
||||
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):
|
||||
return {
|
||||
"Profile": self.profile,
|
||||
"Reason": self.reason,
|
||||
"ApplicationId": self.app_id,
|
||||
}
|
||||
|
||||
|
||||
class ProfileHoldManager(object):
|
||||
def __init__(self, controller):
|
||||
self._holds = {}
|
||||
self._cookie_counter = 0
|
||||
self._controller = controller
|
||||
|
||||
def _callback(self, cookie, app_id):
|
||||
def callback(name):
|
||||
if name == "":
|
||||
log.info("Application '%s' disappeared, releasing hold '%s'" % (app_id, cookie))
|
||||
self.remove(cookie)
|
||||
|
||||
return callback
|
||||
|
||||
def _effective_hold_profile(self):
|
||||
if any(hold.profile == PPD_POWER_SAVER for hold in self._holds.values()):
|
||||
return PPD_POWER_SAVER
|
||||
return PPD_PERFORMANCE
|
||||
|
||||
def _cancel(self, cookie):
|
||||
if cookie not in self._holds:
|
||||
return
|
||||
hold = self._holds.pop(cookie)
|
||||
hold.watch.cancel()
|
||||
exports.send_signal("ProfileReleased", cookie)
|
||||
exports.property_changed("ActiveProfileHolds", self.as_dbus_array())
|
||||
log.info("Releasing hold '%s': profile '%s' by application '%s'" % (cookie, hold.profile, hold.app_id))
|
||||
|
||||
def as_dbus_array(self):
|
||||
return dbus.Array([hold.as_dict() for hold in self._holds.values()], signature="a{sv}")
|
||||
|
||||
def add(self, profile, reason, app_id, caller):
|
||||
cookie = self._cookie_counter
|
||||
self._cookie_counter += 1
|
||||
watch = self._controller.bus.watch_name_owner(caller, self._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(profile)
|
||||
return cookie
|
||||
|
||||
def has(self, cookie):
|
||||
return cookie in self._holds
|
||||
|
||||
def remove(self, cookie):
|
||||
self._cancel(cookie)
|
||||
if len(self._holds) != 0:
|
||||
new_profile = self._effective_hold_profile()
|
||||
else:
|
||||
new_profile = self._controller.base_profile
|
||||
self._controller.switch_profile(new_profile)
|
||||
|
||||
def clear(self):
|
||||
for cookie in self._holds:
|
||||
self._cancel(cookie)
|
||||
|
||||
|
||||
class Controller(exports.interfaces.ExportableInterface):
|
||||
def __init__(self, bus, tuned_interface):
|
||||
super(Controller, self).__init__()
|
||||
self._bus = bus
|
||||
self._tuned_interface = tuned_interface
|
||||
self._profile_holds = ProfileHoldManager(self)
|
||||
self._performance_degraded = PerformanceDegraded.NONE
|
||||
self._cmd = commands()
|
||||
self._terminate = threading.Event()
|
||||
self.load_config()
|
||||
|
||||
def _check_performance_degraded(self):
|
||||
performance_degraded = PerformanceDegraded.NONE
|
||||
if os.path.exists(NO_TURBO_PATH):
|
||||
if int(self._cmd.read_file(NO_TURBO_PATH)) == 1:
|
||||
performance_degraded = PerformanceDegraded.HIGH_OPERATING_TEMPERATURE
|
||||
if os.path.exists(LAP_MODE_PATH):
|
||||
if int(self._cmd.read_file(LAP_MODE_PATH)) == 1:
|
||||
performance_degraded = PerformanceDegraded.LAP_DETECTED
|
||||
if performance_degraded != self._performance_degraded:
|
||||
log.info("Performance degraded: %s" % performance_degraded)
|
||||
self._performance_degraded = performance_degraded
|
||||
exports.property_changed("PerformanceDegraded", performance_degraded)
|
||||
|
||||
def run(self):
|
||||
exports.start()
|
||||
while not self._cmd.wait(self._terminate, 1):
|
||||
self._check_performance_degraded()
|
||||
exports.stop()
|
||||
|
||||
@property
|
||||
def bus(self):
|
||||
return self._bus
|
||||
|
||||
@property
|
||||
def base_profile(self):
|
||||
return self._base_profile
|
||||
|
||||
def terminate(self):
|
||||
self._terminate.set()
|
||||
|
||||
def load_config(self):
|
||||
self._config = PPDConfig(PPD_CONFIG_FILE)
|
||||
self._base_profile = self._config.default_profile
|
||||
self.switch_profile(self._config.default_profile)
|
||||
|
||||
def switch_profile(self, profile):
|
||||
if self.active_profile() == profile:
|
||||
return
|
||||
tuned_profile = 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")
|
||||
|
||||
@exports.export("sss", "u")
|
||||
def HoldProfile(self, profile, reason, app_id, caller):
|
||||
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)
|
||||
)
|
||||
return self._profile_holds.add(profile, reason, app_id, caller)
|
||||
|
||||
@exports.export("u", "")
|
||||
def ReleaseProfile(self, cookie, caller):
|
||||
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):
|
||||
pass
|
||||
|
||||
@exports.property_setter("ActiveProfile")
|
||||
def set_active_profile(self, profile):
|
||||
if profile not in self._config.ppd_to_tuned:
|
||||
raise dbus.exceptions.DBusException("Invalid profile '%s'" % profile)
|
||||
self._base_profile = profile
|
||||
self._profile_holds.clear()
|
||||
self.switch_profile(profile)
|
||||
|
||||
@exports.property_getter("ActiveProfile")
|
||||
def get_active_profile(self):
|
||||
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()],
|
||||
signature="a{sv}",
|
||||
)
|
||||
|
||||
@exports.property_getter("Actions")
|
||||
def get_actions(self):
|
||||
return dbus.Array([], signature="s")
|
||||
|
||||
@exports.property_getter("PerformanceDegraded")
|
||||
def get_performance_degraded(self):
|
||||
return self._performance_degraded
|
||||
|
||||
@exports.property_getter("ActiveProfileHolds")
|
||||
def get_active_profile_holds(self):
|
||||
return self._profile_holds.as_dbus_array()
|
||||
16
tuned/ppd/dbus.conf
Normal file
16
tuned/ppd/dbus.conf
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
|
||||
<busconfig>
|
||||
<policy user="root">
|
||||
<allow own="net.hadess.PowerProfiles"/>
|
||||
</policy>
|
||||
|
||||
<policy context="default">
|
||||
<allow send_destination="net.hadess.PowerProfiles" send_interface="net.hadess.PowerProfiles"/>
|
||||
<allow send_destination="net.hadess.PowerProfiles" send_interface="org.freedesktop.DBus.Introspectable"/>
|
||||
<allow send_destination="net.hadess.PowerProfiles" send_interface="org.freedesktop.DBus.Properties"/>
|
||||
<allow send_destination="net.hadess.PowerProfiles" send_interface="org.freedesktop.DBus.Peer"/>
|
||||
</policy>
|
||||
</busconfig>
|
||||
9
tuned/ppd/ppd.conf
Normal file
9
tuned/ppd/ppd.conf
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[main]
|
||||
# The default PPD profile
|
||||
default=balanced
|
||||
|
||||
[profiles]
|
||||
# PPD = TuneD
|
||||
power-saver=powersave
|
||||
balanced=balanced
|
||||
performance=throughput-performance
|
||||
5
tuned/ppd/tuned-ppd.dbus.service
Normal file
5
tuned/ppd/tuned-ppd.dbus.service
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[D-BUS Service]
|
||||
Name=net.hadess.PowerProfiles
|
||||
Exec=/bin/false
|
||||
User=root
|
||||
SystemdService=tuned-ppd.service
|
||||
29
tuned/ppd/tuned-ppd.policy
Normal file
29
tuned/ppd/tuned-ppd.policy
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
|
||||
<vendor>TuneD</vendor>
|
||||
<vendor_url>https://tuned-project.org/</vendor_url>
|
||||
|
||||
<action id="net.hadess.PowerProfiles.HoldProfile">
|
||||
<description>Hold power profile</description>
|
||||
<message>Authentication is required to hold power profiles.</message>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="net.hadess.PowerProfiles.ReleaseProfile">
|
||||
<description>Release power profile</description>
|
||||
<message>Authentication is required to release power profiles.</message>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
</policyconfig>
|
||||
14
tuned/ppd/tuned-ppd.service
Normal file
14
tuned/ppd/tuned-ppd.service
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=PPD-to-TuneD API Translation Daemon
|
||||
Requires=tuned.service
|
||||
After=tuned.service
|
||||
Before=multi-user.target display-manager.target
|
||||
|
||||
[Service]
|
||||
Type=dbus
|
||||
PIDFile=/run/tuned/tuned-ppd.pid
|
||||
BusName=net.hadess.PowerProfiles
|
||||
ExecStart=/usr/sbin/tuned-ppd
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical.target
|
||||
Loading…
Reference in a new issue