diff --git a/cpu_governor_auto_adjust.xml b/cpu_governor_auto_adjust.xml
index 9d944ac..5d550b8 100644
--- a/cpu_governor_auto_adjust.xml
+++ b/cpu_governor_auto_adjust.xml
@@ -3,6 +3,7 @@
Info
true
powersave
+ 5
test_trigger1
@@ -19,7 +20,7 @@
roon
Info
- 2
+ 1
performance
cpu_governor_auto_adjust_for_roon
@@ -43,7 +44,7 @@
cpu_load
Debug
- 5
+ 1
performance
0.35
@@ -57,7 +58,7 @@
roon_arc
Info
- 5
+ 1
performance
/home/martin/dev/roon_arc/RoonServer_log.txt
diff --git a/cpu_governor_auto_adjust/config.py b/cpu_governor_auto_adjust/config.py
index fe0134b..e6bc934 100644
--- a/cpu_governor_auto_adjust/config.py
+++ b/cpu_governor_auto_adjust/config.py
@@ -71,6 +71,14 @@ class Config:
raise ValueError("testMode can only be 'true' or 'false")
return True if value == 'true' else False
+ @cached_property
+ def statistics_interval_in_seconds(self) -> int:
+ _statistics_interval = self._get_single_text_value_from_xpath('statisticsIntervalInSeconds')
+ value = int(_statistics_interval)
+ if value < 1:
+ raise ValueError("statisticsIntervalInSeconds must be greater than 0")
+ return value
+
@cached_property
def default_governor(self) -> str:
_default_governor = self._get_single_text_value_from_xpath('defaultGovernor')
diff --git a/cpu_governor_auto_adjust/schedule.py b/cpu_governor_auto_adjust/schedule.py
index 1051f4b..cafa111 100644
--- a/cpu_governor_auto_adjust/schedule.py
+++ b/cpu_governor_auto_adjust/schedule.py
@@ -41,17 +41,10 @@ class TriggerScheduler(AppClass):
return
self.governor_control.set_governor(governor_name)
-
- async def async_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:
- self.log.debug("callback trigger %s, state: %s", _trigger.name, _trigger.trigger_state)
- await asyncio.sleep(_trigger.config.interval_in_seconds)
async def start_trigger(self, _trigger: Trigger) -> None:
"""Start a new trigger."""
- self.loop.create_task(self.async_trigger(_trigger))
+ self.loop.create_task(_trigger.run())
self.running_triggers.append(_trigger)
async def stop_triggers(self) -> None:
diff --git a/cpu_governor_auto_adjust/trigger.py b/cpu_governor_auto_adjust/trigger.py
index 6e5fe41..2c7c7da 100644
--- a/cpu_governor_auto_adjust/trigger.py
+++ b/cpu_governor_auto_adjust/trigger.py
@@ -3,6 +3,9 @@ from cpu_governor_auto_adjust.config import Config, TriggerTuple
from cpu_governor_auto_adjust.exceptions import MissingConfig, TriggerImportError
from cpu_governor_auto_adjust.governor import Governor, _governor_list
from functools import cached_property
+from time import monotonic
+from datetime import datetime
+import asyncio
class Trigger(AppClass):
@@ -10,10 +13,21 @@ class Trigger(AppClass):
super().__init__(_config)
self.active: bool = False
self.log.setLevel(self.config.loglevel.upper())
+ self._timestamp_last_statistics = monotonic()
def __hash__(self):
return super().__hash__(hash(self.name))
+ def log_statistics(self):
+ instance_vars = {}
+
+ for key, value in self.__dict__.items():
+ if not key.startswith("_") and isinstance(value, (str, int, float, bool, list, dict, datetime, Governor)):
+ if not isinstance(value, (int, float, Governor)):
+ value = str(value)
+ instance_vars[key] = value
+ self.log.info("info and statistics: %s", instance_vars)
+
@property
def trigger_state(self) -> str:
return "active" if self.active else "not active"
@@ -41,18 +55,20 @@ class Trigger(AppClass):
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
-
- while True:
- raise TriggerImportError(
- "the Trigger class can't used directly, but must be inherited in a trigger specific class"
- )
-
- async def async_run(self) -> None:
+ def trigger_code(self) -> None:
# this is the main function of the trigger class and can be used to execute a callback function
+ raise TriggerImportError(
+ "the Trigger class can't used directly, but must be inherited in a trigger specific class"
+ )
+
+ async def run(self) -> None:
while True:
- raise TriggerImportError(
- "the Trigger class can't used directly, but must be inherited in a trigger specific class"
- )
+ self.log.debug("run trigger code of %s", self.__class__.__name__)
+ if self._timestamp_last_statistics + int(self._config.statistics_interval_in_seconds) < monotonic():
+ self.log_statistics()
+ self._timestamp_last_statistics = monotonic()
+ self.trigger_code()
+ self.log.debug("trigger state: %s", self.trigger_state)
+ await asyncio.sleep(self.config.interval_in_seconds)
+
\ No newline at end of file
diff --git a/cpu_governor_auto_adjust/triggers/cpu_load.py b/cpu_governor_auto_adjust/triggers/cpu_load.py
index ac570f1..464264e 100644
--- a/cpu_governor_auto_adjust/triggers/cpu_load.py
+++ b/cpu_governor_auto_adjust/triggers/cpu_load.py
@@ -2,7 +2,6 @@ from cpu_governor_auto_adjust.trigger import Trigger
from cpu_governor_auto_adjust.config import Config
from functools import cached_property
from os import getloadavg
-import asyncio
class CpuLoadTrigger(Trigger):
@@ -55,34 +54,29 @@ class CpuLoadTrigger(Trigger):
def current_load_average_over_under_threshold(self) -> bool:
return any(load <= threshold for load, threshold in zip(self.current_load, self.low_threshold))
- async def check_load(self) -> None:
- while True:
- current_active = self.active
- new_high_load_active = self.current_load_average_over_high_threshold
- new_low_load_active = self.current_load_average_over_under_threshold
+ def trigger_code(self) -> None:
+ current_active = self.active
+ new_high_load_active = self.current_load_average_over_high_threshold
+ new_low_load_active = self.current_load_average_over_under_threshold
+
+ if not current_active and new_high_load_active:
+ self.log.info(
+ "activating trigger, load: %s, high threshold: %s, governor: %s",
+ self.current_load, self.high_threshold, self.governor.name
+ )
+ self.active = True
- if not current_active and new_high_load_active:
- self.log.info(
- "activating trigger, load: %s, high threshold: %s, governor: %s",
- self.current_load, self.high_threshold, self.governor.name
- )
- self.active = True
-
- elif current_active and new_high_load_active:
- self.log.debug(
- "trigger is already active, load: %s, high threshold: %s",
- self.current_load, self.high_threshold
- )
-
- elif current_active and new_low_load_active:
- self.log.info(
- "deactivating trigger, load: %s, low threshold: %s, governor: %s",
- self.current_load, self.low_threshold, self.governor.name
- )
- self.active = False
- else:
- self.log.debug("trigger is already inactive, load: %s, threshold: %s", self.current_load, self.high_threshold)
- await asyncio.sleep(1)
-
- async def async_run(self) -> None:
- await self.check_load()
+ elif current_active and new_high_load_active:
+ self.log.debug(
+ "trigger is already active, load: %s, high threshold: %s",
+ self.current_load, self.high_threshold
+ )
+
+ elif current_active and new_low_load_active:
+ self.log.info(
+ "deactivating trigger, load: %s, low threshold: %s, governor: %s",
+ self.current_load, self.low_threshold, self.governor.name
+ )
+ self.active = False
+ else:
+ self.log.debug("trigger is already inactive, load: %s, threshold: %s", self.current_load, self.high_threshold)
diff --git a/cpu_governor_auto_adjust/triggers/log.py b/cpu_governor_auto_adjust/triggers/log.py
index 9cfc983..5fb2d74 100644
--- a/cpu_governor_auto_adjust/triggers/log.py
+++ b/cpu_governor_auto_adjust/triggers/log.py
@@ -3,7 +3,6 @@ from cpu_governor_auto_adjust.config import Config
from datetime import datetime, timedelta
from functools import cached_property
from dataclasses import dataclass
-import asyncio
import os
@@ -48,6 +47,8 @@ class LogTrigger(Trigger):
super().__init__(_config)
self.timestamp_last_active_change = datetime.now() - timedelta(minutes=self.timeout_in_minutes + 1)
self.line_count = 0
+ self._file = None
+ self._current_inode = None
@cached_property
def file(self) -> str:
@@ -75,15 +76,12 @@ class LogTrigger(Trigger):
self.timestamp_last_active_change = datetime.now()
if not self.active:
self.log.info("activating trigger, log file: %s, trigger strings: %s, governor: %s", self.file, self.trigger_strings, self.governor.name)
- self.active = True
- self.log.info("Statistics: %s", self.get_statistic_info())
-
+ self.active = True
def set_inactive(self) -> None:
if self.active:
self.log.info("deactivating trigger, log file: %s, trigger strings: %s, governor: %s", self.file, self.trigger_strings, self.governor.name)
self.active = False
- self.log.info("Statistics: %s", self.get_statistic_info())
def _process_line(self, line: str) -> None:
for trigger_string in self.trigger_strings:
@@ -105,46 +103,31 @@ class LogTrigger(Trigger):
else:
self.log.debug("substrings condition not met")
self.log.debug("No trigger strings matched in line: %s", line.strip())
-
- def get_statistic_info(self) -> dict[str, str]:
- return {
- 'file': self.file,
- 'active': str(self.active),
- 'line_count': str(self.line_count),
- 'timestamp_last_active_change': str(self.timestamp_last_active_change),
- 'timeout_in_minutes': str(self.timeout_in_minutes),
- }
- async def read_log(self) -> None:
- while True:
- try:
- current_inode = os.stat(self.file).st_ino # Get initial inode
+ def trigger_code(self) -> None:
+ try:
+ if self._file is None:
+ self.log.info("Opening file: %s", self.file)
+ self._file = open(self.file, 'r')
+ self._current_inode = os.stat(self.file).st_ino
+ self._file.seek(0, 2) # Move to the end of the file
+
+ # Detect if file has been rotated
+ if os.stat(self.file).st_ino != self._current_inode:
+ self.log.info("File %s rolled over. Reopening...", self.file)
+ self._file.close()
+ self._file = open(self.file, 'r')
+ self._current_inode = os.stat(self.file).st_ino
+
+ line = self._file.readline()
+ while line:
+ self.line_count += 1
+ self._process_line(line)
+ line = self._file.readline()
+
+ # Check if the trigger has not been triggered for the timeout period
+ if (datetime.now() - self.timestamp_last_active_change) > timedelta(minutes=self.timeout_in_minutes):
+ self.set_inactive()
- with open(self.file, 'r') as _file:
- _file.seek(0, 2) # Move to the end of the file
- while True:
- line = _file.readline()
- if line:
- self.line_count += 1
- if self.line_count % 100 == 0:
- self.log.info("Statistics: %s", self.get_statistic_info())
- else:
- self.log.debug("Statistics: %s", self.get_statistic_info())
-
- self._process_line(line)
- else:
- await asyncio.sleep(0.1)
- # Check if the trigger has not been triggered for the timeout period
- if (datetime.now() - self.timestamp_last_active_change) > timedelta(minutes=self.timeout_in_minutes):
- self.set_inactive()
-
- # Detect if file has been rotated
- if os.stat(self.file).st_ino != current_inode:
- self.log.info("File %s rolled over. Reopening...", self.file)
- break # Exit inner loop to reopen file
- except FileNotFoundError:
- self.log.debug("file not found: %s", self.file)
- await asyncio.sleep(1)
-
- async def async_run(self) -> None:
- await self.read_log()
+ except FileNotFoundError:
+ self.log.debug("file not found: %s", self.file)
diff --git a/cpu_governor_auto_adjust/triggers/roon.py b/cpu_governor_auto_adjust/triggers/roon.py
index 4eff256..a147ece 100644
--- a/cpu_governor_auto_adjust/triggers/roon.py
+++ b/cpu_governor_auto_adjust/triggers/roon.py
@@ -20,7 +20,8 @@ class RoonTrigger(Trigger):
def __init__(self, _config: Config) -> None:
super().__init__(_config)
self.set_status()
-
+ self.roonapi.register_state_callback(self.roon_state_callback, "zones_changed")
+
def set_status(self) -> None:
zones_state = list()
@@ -99,5 +100,6 @@ class RoonTrigger(Trigger):
)
self.set_status()
- async def async_run(self) -> None:
- self.roonapi.register_state_callback(self.roon_state_callback, "zones_changed")
+ def trigger_code(self) -> None:
+ # this check works with the callback function of roonapi
+ pass
diff --git a/cpu_governor_auto_adjust/triggers/test_trigger1.py b/cpu_governor_auto_adjust/triggers/test_trigger1.py
index eda7a4c..f3f98a1 100644
--- a/cpu_governor_auto_adjust/triggers/test_trigger1.py
+++ b/cpu_governor_auto_adjust/triggers/test_trigger1.py
@@ -1,15 +1,12 @@
from cpu_governor_auto_adjust.trigger import Trigger
from cpu_governor_auto_adjust.config import Config
import random
-import asyncio
class TestTrigger1(Trigger):
def __init__(self, _config: Config) -> None:
super().__init__(_config)
- async def async_run(self) -> None:
- while True:
- choices = [False, True]
- self.log.debug("run check code of %s", self.__class__.__name__)
- self.active = random.choice(choices)
- await asyncio.sleep(1)
+ def trigger_code(self) -> None:
+ choices = [False, True]
+ self.log.debug("run check code of %s", self.__class__.__name__)
+ self.active = random.choice(choices)
diff --git a/cpu_governor_auto_adjust/triggers/time.py b/cpu_governor_auto_adjust/triggers/time.py
index e3b44ce..ccd1fd7 100644
--- a/cpu_governor_auto_adjust/triggers/time.py
+++ b/cpu_governor_auto_adjust/triggers/time.py
@@ -1,7 +1,6 @@
from cpu_governor_auto_adjust.trigger import Trigger
from cpu_governor_auto_adjust.config import Config
from datetime import datetime, time
-import asyncio
class TimeTrigger(Trigger):
@@ -15,16 +14,12 @@ class TimeTrigger(Trigger):
def end_time(self) -> time:
return datetime.strptime(self.config.custom_config['endTime'], '%H:%M').time()
- async def check_time(self) -> None:
- while True:
- active_current = self.active
- active_new = self.start_time() <= datetime.now().time() <= self.end_time()
- if not active_current and active_new:
- self.log.info("activating trigger, start time: %s, end time: %s, governor: %s", self.start_time(), self.end_time(), self.governor.name)
- elif active_current and not active_new:
- self.log.info("deactivating trigger, start time: %s, end time: %s, governor: %s", self.start_time(), self.end_time(), self.governor.name)
- self.active = active_new
- await asyncio.sleep(1)
-
- async def async_run(self) -> None:
- await self.check_time()
+ def trigger_code(self) -> None:
+ active_current = self.active
+ active_new = self.start_time() <= datetime.now().time() <= self.end_time()
+ if not active_current and active_new:
+ self.log.info("activating trigger, start time: %s, end time: %s, governor: %s", self.start_time(), self.end_time(), self.governor.name)
+ elif active_current and not active_new:
+ self.log.info("deactivating trigger, start time: %s, end time: %s, governor: %s", self.start_time(), self.end_time(), self.governor.name)
+ self.active = active_new
+
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index c2bb5b9..8730da0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "cpu_governor_auto_adjust"
-version = "0.4.0"
+version = "0.5.6"
description = "This application has been developed to automatically change cpu governor based on certain triggers."
authors = [
{ name = "Martin Reurekas", email = "martin@semrks.nl" }