From 230ed62bd0bbe79cf7fed6fc0dc4b1666a781381 Mon Sep 17 00:00:00 2001 From: martin Date: Tue, 1 Apr 2025 00:26:16 +0200 Subject: [PATCH] log info and statistics of every trigger --- cpu_governor_auto_adjust.xml | 7 +- cpu_governor_auto_adjust/config.py | 8 ++ cpu_governor_auto_adjust/schedule.py | 9 +-- cpu_governor_auto_adjust/trigger.py | 40 +++++++--- cpu_governor_auto_adjust/triggers/cpu_load.py | 56 +++++++------- cpu_governor_auto_adjust/triggers/log.py | 75 +++++++------------ cpu_governor_auto_adjust/triggers/roon.py | 8 +- .../triggers/test_trigger1.py | 11 +-- cpu_governor_auto_adjust/triggers/time.py | 23 +++--- pyproject.toml | 2 +- 10 files changed, 114 insertions(+), 125 deletions(-) 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" }