added governor control code

This commit is contained in:
2024-12-20 17:28:35 +01:00
parent 2233a0181a
commit 2a1cfa9f72
6 changed files with 69 additions and 26 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ from pathlib import Path
from logger import getLogger
from mapping import trigger_mapping
from config import Config
from governor import Governor
from governor import GovernorControl
from schedule import TriggerScheduler
@@ -15,7 +15,7 @@ async def main() -> None:
if config.testmode:
log.warning("starting in testmode, cpu adjustments have been disabled")
governor = Governor(config)
governor = GovernorControl(config)
scheduler = TriggerScheduler(config)
for triggertuple in config.triggertuples:
_trigger = trigger_mapping[triggertuple.name](config)
+1
View File
@@ -31,4 +31,5 @@
</customConfig>
</trigger>
</triggers>
<powersaveDelayInMinutes>5</powersaveDelayInMinutes>
</cpuGovernorAutoAdjust>
+29 -8
View File
@@ -2,12 +2,26 @@ from app_class import AppClass
from config import Config
from cpufreq import cpuFreq, cpufreq # type: ignore
from functools import cached_property
from mapping import governor_priority_mapping
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:
super().__init__(_config)
self.log.info("current governor is: %s", self.current_governor)
@@ -21,20 +35,27 @@ class Governor(AppClass):
return None
@property
def current_governor(self) -> Optional[str]:
def current_governor(self) -> Optional[Governor]:
if self._cpufreq is None:
return None
_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:
raise GovernorNotFound("Unable to retrieve current governor")
if len(_governor) > 1:
self.log.error("multiple governors have been set, which is not expected.")
# returning governor with highest priority based on performance
for gov in _governor:
if governor_priority_mapping[gov] < governor_priority_mapping[_governor_to_return]:
_governor_to_return == gov
return _governor_to_return
t_gov = self._establish_governor(gov)
if t_gov.priority < _governor_to_return.priority:
_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:
self.log.debug(
-11
View File
@@ -7,14 +7,3 @@ _trigger_mapping = {
'roon': RoonTrigger
}
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
View File
@@ -1,29 +1,44 @@
from app_class import AppClass
from config import Config
from trigger import Trigger
from governor import GovernorControl, Governor, _governor_list
import asyncio
from asyncio import Task
from functools import cached_property
class TriggerScheduler(AppClass):
def __init__(self, _config: Config) -> None:
super().__init__(_config)
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:
"""Run a callback_trigger with a specific name and check its status at a given interval."""
await _trigger.async_run()
while True:
trigger_state = "active" if _trigger.active else "not active"
self.log.debug("Checking state of trigger %s, state: %s", _trigger.name, trigger_state)
self.log.debug("Checking state of trigger %s, state: %s", _trigger.name, _trigger.trigger_state)
await asyncio.sleep(_trigger.config.interval_in_seconds)
async def run_once_trigger(self, _trigger: Trigger) -> None:
"""Run a trigger with a specific name at a given interval."""
while True:
_trigger.run()
trigger_state = "active" if _trigger.active else "not active"
self.log.debug("Checking state of trigger %s, state: %s", _trigger.name, trigger_state)
self.log.debug("Checking state of trigger %s, state: %s", _trigger.name, _trigger.trigger_state)
await asyncio.sleep(_trigger.config.interval_in_seconds)
def start_trigger(self, _trigger: Trigger) -> None:
@@ -32,6 +47,7 @@ class TriggerScheduler(AppClass):
task = asyncio.create_task(self.callback_trigger(_trigger))
else:
task = asyncio.create_task(self.run_once_trigger(_trigger))
self.running_triggers.append(_trigger)
self.tasks.append(task)
async def stop_triggers(self) -> None:
@@ -44,6 +60,7 @@ class TriggerScheduler(AppClass):
"""Run the scheduler and keep it alive until stopped."""
try:
while True:
self.establish_preferred_governor()
await asyncio.sleep(1) # Keep the main function alive
except asyncio.exceptions.CancelledError:
self.log.info("Exiting, stopping all triggers")
+16 -1
View File
@@ -1,12 +1,21 @@
from app_class import AppClass
from config import Config, TriggerTuple
from exceptions import MissingConfig, TriggerImportError
from governor import Governor, _governor_list
from functools import cached_property
class Trigger(AppClass):
def __init__(self, _config: Config) -> None:
super().__init__(_config)
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
def name(self) -> str:
@@ -24,6 +33,13 @@ class Trigger(AppClass):
raise MissingConfig(f"Trigger {self.name} hasn't been properly configured")
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:
# 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(
"the Trigger class can't used directly, but must be inherited in a trigger specific class"
)