added governor control code
This commit is contained in:
@@ -4,7 +4,7 @@ from pathlib import Path
|
|||||||
from logger import getLogger
|
from logger import getLogger
|
||||||
from mapping import trigger_mapping
|
from mapping import trigger_mapping
|
||||||
from config import Config
|
from config import Config
|
||||||
from governor import Governor
|
from governor import GovernorControl
|
||||||
from schedule import TriggerScheduler
|
from schedule import TriggerScheduler
|
||||||
|
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ async def main() -> None:
|
|||||||
if config.testmode:
|
if config.testmode:
|
||||||
log.warning("starting in testmode, cpu adjustments have been disabled")
|
log.warning("starting in testmode, cpu adjustments have been disabled")
|
||||||
|
|
||||||
governor = Governor(config)
|
governor = GovernorControl(config)
|
||||||
scheduler = TriggerScheduler(config)
|
scheduler = TriggerScheduler(config)
|
||||||
for triggertuple in config.triggertuples:
|
for triggertuple in config.triggertuples:
|
||||||
_trigger = trigger_mapping[triggertuple.name](config)
|
_trigger = trigger_mapping[triggertuple.name](config)
|
||||||
|
|||||||
@@ -31,4 +31,5 @@
|
|||||||
</customConfig>
|
</customConfig>
|
||||||
</trigger>
|
</trigger>
|
||||||
</triggers>
|
</triggers>
|
||||||
|
<powersaveDelayInMinutes>5</powersaveDelayInMinutes>
|
||||||
</cpuGovernorAutoAdjust>
|
</cpuGovernorAutoAdjust>
|
||||||
|
|||||||
+29
-8
@@ -2,12 +2,26 @@ from app_class import AppClass
|
|||||||
from config import Config
|
from config import Config
|
||||||
from cpufreq import cpuFreq, cpufreq # type: ignore
|
from cpufreq import cpuFreq, cpufreq # type: ignore
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from mapping import governor_priority_mapping
|
|
||||||
from exceptions import GovernorNotFound
|
from exceptions import GovernorNotFound
|
||||||
from typing import Optional
|
from typing import Optional, NamedTuple
|
||||||
|
|
||||||
|
|
||||||
|
class Governor(NamedTuple):
|
||||||
|
name: str
|
||||||
|
priority: int
|
||||||
|
|
||||||
|
|
||||||
|
_governor_list: list[Governor] = [
|
||||||
|
Governor("performance", 0),
|
||||||
|
Governor("schedutil", 1),
|
||||||
|
Governor("ondemand", 2),
|
||||||
|
Governor("conservative", 3),
|
||||||
|
Governor("userspace", 4),
|
||||||
|
Governor("powersave", 5)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Governor(AppClass):
|
class GovernorControl(AppClass):
|
||||||
def __init__(self, _config: Config) -> None:
|
def __init__(self, _config: Config) -> None:
|
||||||
super().__init__(_config)
|
super().__init__(_config)
|
||||||
self.log.info("current governor is: %s", self.current_governor)
|
self.log.info("current governor is: %s", self.current_governor)
|
||||||
@@ -21,20 +35,27 @@ class Governor(AppClass):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_governor(self) -> Optional[str]:
|
def current_governor(self) -> Optional[Governor]:
|
||||||
if self._cpufreq is None:
|
if self._cpufreq is None:
|
||||||
return None
|
return None
|
||||||
_governor = set(self._cpufreq.get_governors().values())
|
_governor = set(self._cpufreq.get_governors().values())
|
||||||
_governor_to_return = list(_governor)[0]
|
_governor_to_return = self._establish_governor(list(_governor)[0])
|
||||||
if len(_governor) == 0:
|
if len(_governor) == 0:
|
||||||
raise GovernorNotFound("Unable to retrieve current governor")
|
raise GovernorNotFound("Unable to retrieve current governor")
|
||||||
if len(_governor) > 1:
|
if len(_governor) > 1:
|
||||||
self.log.error("multiple governors have been set, which is not expected.")
|
self.log.error("multiple governors have been set, which is not expected.")
|
||||||
# returning governor with highest priority based on performance
|
# returning governor with highest priority based on performance
|
||||||
for gov in _governor:
|
for gov in _governor:
|
||||||
if governor_priority_mapping[gov] < governor_priority_mapping[_governor_to_return]:
|
t_gov = self._establish_governor(gov)
|
||||||
_governor_to_return == gov
|
if t_gov.priority < _governor_to_return.priority:
|
||||||
return _governor_to_return
|
_governor_to_return == t_gov
|
||||||
|
return _governor_to_return
|
||||||
|
|
||||||
|
def _establish_governor(self, governor_name: str) -> Governor:
|
||||||
|
governor = next((g for g in _governor_list if g.name == governor_name), None)
|
||||||
|
if governor is None:
|
||||||
|
raise RuntimeError("could not establish governor")
|
||||||
|
return governor
|
||||||
|
|
||||||
def set_governor(self, governor_name: str) -> None:
|
def set_governor(self, governor_name: str) -> None:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
|
|||||||
-11
@@ -7,14 +7,3 @@ _trigger_mapping = {
|
|||||||
'roon': RoonTrigger
|
'roon': RoonTrigger
|
||||||
}
|
}
|
||||||
trigger_mapping = MappingProxyType(_trigger_mapping)
|
trigger_mapping = MappingProxyType(_trigger_mapping)
|
||||||
|
|
||||||
_governor_priority_mapping = {
|
|
||||||
'performance': 0,
|
|
||||||
'schedutil': 1,
|
|
||||||
'ondemand': 2,
|
|
||||||
'conservative': 3,
|
|
||||||
'userspace': 4,
|
|
||||||
'performance': 5
|
|
||||||
}
|
|
||||||
|
|
||||||
governor_priority_mapping = MappingProxyType(_governor_priority_mapping)
|
|
||||||
|
|||||||
+21
-4
@@ -1,29 +1,44 @@
|
|||||||
from app_class import AppClass
|
from app_class import AppClass
|
||||||
from config import Config
|
from config import Config
|
||||||
from trigger import Trigger
|
from trigger import Trigger
|
||||||
|
from governor import GovernorControl, Governor, _governor_list
|
||||||
import asyncio
|
import asyncio
|
||||||
from asyncio import Task
|
from asyncio import Task
|
||||||
|
from functools import cached_property
|
||||||
|
|
||||||
|
|
||||||
class TriggerScheduler(AppClass):
|
class TriggerScheduler(AppClass):
|
||||||
def __init__(self, _config: Config) -> None:
|
def __init__(self, _config: Config) -> None:
|
||||||
super().__init__(_config)
|
super().__init__(_config)
|
||||||
self.tasks: list[Task] = []
|
self.tasks: list[Task] = []
|
||||||
|
self.running_triggers: list[Trigger] = []
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def governor_control(self) -> GovernorControl:
|
||||||
|
return GovernorControl(self._config)
|
||||||
|
|
||||||
|
def establish_preferred_governor(self) -> str:
|
||||||
|
preferred_governor: Governor = [gov for gov in _governor_list if gov.name == 'powersave'].pop()
|
||||||
|
for trigger in self.running_triggers:
|
||||||
|
self.log.debug('trigger %s, state: %s, preferred governor: %s', trigger.name, trigger.trigger_state, trigger.governor.name)
|
||||||
|
if trigger.active:
|
||||||
|
if trigger.governor.priority < preferred_governor.priority:
|
||||||
|
preferred_governor = trigger.governor
|
||||||
|
self.log.debug('preferred governor: %s', preferred_governor.name)
|
||||||
|
return preferred_governor.name
|
||||||
|
|
||||||
async def callback_trigger(self, _trigger: Trigger) -> None:
|
async def callback_trigger(self, _trigger: Trigger) -> None:
|
||||||
"""Run a callback_trigger with a specific name and check its status at a given interval."""
|
"""Run a callback_trigger with a specific name and check its status at a given interval."""
|
||||||
await _trigger.async_run()
|
await _trigger.async_run()
|
||||||
while True:
|
while True:
|
||||||
trigger_state = "active" if _trigger.active else "not active"
|
self.log.debug("Checking state of trigger %s, state: %s", _trigger.name, _trigger.trigger_state)
|
||||||
self.log.debug("Checking state of trigger %s, state: %s", _trigger.name, trigger_state)
|
|
||||||
await asyncio.sleep(_trigger.config.interval_in_seconds)
|
await asyncio.sleep(_trigger.config.interval_in_seconds)
|
||||||
|
|
||||||
async def run_once_trigger(self, _trigger: Trigger) -> None:
|
async def run_once_trigger(self, _trigger: Trigger) -> None:
|
||||||
"""Run a trigger with a specific name at a given interval."""
|
"""Run a trigger with a specific name at a given interval."""
|
||||||
while True:
|
while True:
|
||||||
_trigger.run()
|
_trigger.run()
|
||||||
trigger_state = "active" if _trigger.active else "not active"
|
self.log.debug("Checking state of trigger %s, state: %s", _trigger.name, _trigger.trigger_state)
|
||||||
self.log.debug("Checking state of trigger %s, state: %s", _trigger.name, trigger_state)
|
|
||||||
await asyncio.sleep(_trigger.config.interval_in_seconds)
|
await asyncio.sleep(_trigger.config.interval_in_seconds)
|
||||||
|
|
||||||
def start_trigger(self, _trigger: Trigger) -> None:
|
def start_trigger(self, _trigger: Trigger) -> None:
|
||||||
@@ -32,6 +47,7 @@ class TriggerScheduler(AppClass):
|
|||||||
task = asyncio.create_task(self.callback_trigger(_trigger))
|
task = asyncio.create_task(self.callback_trigger(_trigger))
|
||||||
else:
|
else:
|
||||||
task = asyncio.create_task(self.run_once_trigger(_trigger))
|
task = asyncio.create_task(self.run_once_trigger(_trigger))
|
||||||
|
self.running_triggers.append(_trigger)
|
||||||
self.tasks.append(task)
|
self.tasks.append(task)
|
||||||
|
|
||||||
async def stop_triggers(self) -> None:
|
async def stop_triggers(self) -> None:
|
||||||
@@ -44,6 +60,7 @@ class TriggerScheduler(AppClass):
|
|||||||
"""Run the scheduler and keep it alive until stopped."""
|
"""Run the scheduler and keep it alive until stopped."""
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
|
self.establish_preferred_governor()
|
||||||
await asyncio.sleep(1) # Keep the main function alive
|
await asyncio.sleep(1) # Keep the main function alive
|
||||||
except asyncio.exceptions.CancelledError:
|
except asyncio.exceptions.CancelledError:
|
||||||
self.log.info("Exiting, stopping all triggers")
|
self.log.info("Exiting, stopping all triggers")
|
||||||
|
|||||||
+16
-1
@@ -1,12 +1,21 @@
|
|||||||
from app_class import AppClass
|
from app_class import AppClass
|
||||||
from config import Config, TriggerTuple
|
from config import Config, TriggerTuple
|
||||||
from exceptions import MissingConfig, TriggerImportError
|
from exceptions import MissingConfig, TriggerImportError
|
||||||
|
from governor import Governor, _governor_list
|
||||||
|
from functools import cached_property
|
||||||
|
|
||||||
|
|
||||||
class Trigger(AppClass):
|
class Trigger(AppClass):
|
||||||
def __init__(self, _config: Config) -> None:
|
def __init__(self, _config: Config) -> None:
|
||||||
super().__init__(_config)
|
super().__init__(_config)
|
||||||
self.active: bool = False
|
self.active: bool = False
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return super().__hash__(hash(self.name))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def trigger_state(self) -> str:
|
||||||
|
return "active" if self.active else "not active"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@@ -24,6 +33,13 @@ class Trigger(AppClass):
|
|||||||
raise MissingConfig(f"Trigger {self.name} hasn't been properly configured")
|
raise MissingConfig(f"Trigger {self.name} hasn't been properly configured")
|
||||||
return _config
|
return _config
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def governor(self) -> Governor:
|
||||||
|
_governor = next((gov for gov in _governor_list if self.config.governor == gov.name), None)
|
||||||
|
if _governor is None:
|
||||||
|
raise ValueError("unknown governor: %s", _governor)
|
||||||
|
return _governor
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
# this is the main function of the trigger class and has to stay active by using a while True loop
|
# this is the main function of the trigger class and has to stay active by using a while True loop
|
||||||
|
|
||||||
@@ -39,4 +55,3 @@ class Trigger(AppClass):
|
|||||||
raise TriggerImportError(
|
raise TriggerImportError(
|
||||||
"the Trigger class can't used directly, but must be inherited in a trigger specific class"
|
"the Trigger class can't used directly, but must be inherited in a trigger specific class"
|
||||||
)
|
)
|
||||||
|
|
||||||
Reference in New Issue
Block a user