LogTrigger added support for substrings and added statistics
This commit is contained in:
@@ -66,7 +66,13 @@
|
|||||||
<customConfig>
|
<customConfig>
|
||||||
<logFile>/home/martin/dev/roon_arc/RoonServer_log.txt</logFile>
|
<logFile>/home/martin/dev/roon_arc/RoonServer_log.txt</logFile>
|
||||||
<timeoutInMinutes>1</timeoutInMinutes>
|
<timeoutInMinutes>1</timeoutInMinutes>
|
||||||
<triggerWord>[Broker:Mobile]</triggerWord>
|
<triggerString>
|
||||||
|
<string>[Broker:Mobile]</string>
|
||||||
|
<substrings>
|
||||||
|
<condition>AND</condition>
|
||||||
|
<substring>got playbackinfo</substring>
|
||||||
|
</substrings>
|
||||||
|
</triggerString>
|
||||||
</customConfig>
|
</customConfig>
|
||||||
</trigger>
|
</trigger>
|
||||||
</triggers>
|
</triggers>
|
||||||
|
|||||||
@@ -2,14 +2,46 @@ from cpu_governor_auto_adjust.trigger import Trigger
|
|||||||
from cpu_governor_auto_adjust.config import Config
|
from cpu_governor_auto_adjust.config import Config
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
from dataclasses import dataclass
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TriggerString:
|
||||||
|
json_data: dict[str, str]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def string(self) -> str:
|
||||||
|
if 'string' in self.json_data:
|
||||||
|
return self.json_data['string']
|
||||||
|
else:
|
||||||
|
raise ValueError("TriggerString must contain 'string'")
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def substrings(self) -> list[str]:
|
||||||
|
if 'substrings' in self.json_data:
|
||||||
|
return self.json_data['substrings']['substring']
|
||||||
|
return []
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def condition(self) -> str:
|
||||||
|
if not self.substrings:
|
||||||
|
return 'OR'
|
||||||
|
if not 'condition' in self.json_data['substrings']:
|
||||||
|
raise ValueError("substrings must contain 'condition'")
|
||||||
|
if not self.json_data['substrings']['condition'] in ['OR', 'AND']:
|
||||||
|
raise ValueError("substrings condition must be 'OR' or 'AND'")
|
||||||
|
return self.json_data['substrings']['condition']
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"TriggerString(string={self.string}, substrings={self.substrings}, condition={self.condition})"
|
||||||
|
|
||||||
class LogTrigger(Trigger):
|
class LogTrigger(Trigger):
|
||||||
def __init__(self, _config: Config) -> None:
|
def __init__(self, _config: Config) -> None:
|
||||||
super().__init__(_config)
|
super().__init__(_config)
|
||||||
self.timestamp_last_active_change = datetime.now() - timedelta(minutes=self.timeout_in_minutes + 1)
|
self.timestamp_last_active_change = datetime.now() - timedelta(minutes=self.timeout_in_minutes + 1)
|
||||||
|
self.line_count = 0
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def file(self) -> str:
|
def file(self) -> str:
|
||||||
@@ -20,35 +52,59 @@ class LogTrigger(Trigger):
|
|||||||
return float(self.config.custom_config['timeoutInMinutes'])
|
return float(self.config.custom_config['timeoutInMinutes'])
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def trigger_words(self) -> list:
|
def trigger_strings(self) -> list[TriggerString]:
|
||||||
_trigger_words = []
|
_trigger_strings = []
|
||||||
if 'triggerWord' not in self.config.custom_config:
|
if 'triggerString' not in self.config.custom_config:
|
||||||
self.config.custom_config['triggerWords'] = _trigger_words
|
return _trigger_strings
|
||||||
elif isinstance(self.config.custom_config['triggerWord'], str):
|
elif isinstance(self.config.custom_config['triggerString'], dict):
|
||||||
_trigger_words.append(self.config.custom_config['triggerWord'])
|
_trigger_strings.append(TriggerString(self.config.custom_config['triggerString']))
|
||||||
elif isinstance(self.config.custom_config['triggerWord'], list):
|
elif isinstance(self.config.custom_config['triggerString'], list):
|
||||||
_trigger_words = self.config.custom_config['triggerWord']
|
for trigger_string in self.config.custom_config['triggerString']:
|
||||||
|
_trigger_strings.append(TriggerString(trigger_string))
|
||||||
else:
|
else:
|
||||||
raise ValueError("triggerWord must be a string or a list of strings")
|
raise ValueError("triggerString must be a dict or a list of dicts")
|
||||||
return _trigger_words
|
return _trigger_strings
|
||||||
|
|
||||||
def set_active(self) -> None:
|
def set_active(self) -> None:
|
||||||
self.timestamp_last_active_change = datetime.now()
|
self.timestamp_last_active_change = datetime.now()
|
||||||
if not self.active:
|
if not self.active:
|
||||||
self.log.info("activating trigger, log file: %s, trigger words: %s, governor: %s", self.file, self.trigger_words, self.governor.name)
|
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.active = True
|
||||||
|
|
||||||
def set_inactive(self) -> None:
|
def set_inactive(self) -> None:
|
||||||
if self.active:
|
if self.active:
|
||||||
self.log.info("deactivating trigger, log file: %s, trigger words: %s, governor: %s", self.file, self.trigger_words, self.governor.name)
|
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.active = False
|
||||||
|
|
||||||
def _process_line(self, line: str) -> None:
|
def _process_line(self, line: str) -> None:
|
||||||
for word in self.trigger_words:
|
for trigger_string in self.trigger_strings:
|
||||||
if word in line:
|
if trigger_string.string in line:
|
||||||
|
if not trigger_string.substrings:
|
||||||
self.log.debug(line.strip())
|
self.log.debug(line.strip())
|
||||||
self.set_active()
|
self.set_active()
|
||||||
return
|
return
|
||||||
|
elif trigger_string.substrings and trigger_string.condition == 'OR':
|
||||||
|
if any(substring in line for substring in trigger_string.substrings):
|
||||||
|
self.log.debug(line.strip())
|
||||||
|
self.set_active()
|
||||||
|
return
|
||||||
|
elif trigger_string.substrings and trigger_string.condition == 'AND':
|
||||||
|
if all(substring in line for substring in trigger_string.substrings):
|
||||||
|
self.log.debug(line.strip())
|
||||||
|
self.set_active()
|
||||||
|
return
|
||||||
|
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:
|
async def read_log(self) -> None:
|
||||||
while True:
|
while True:
|
||||||
@@ -60,6 +116,12 @@ class LogTrigger(Trigger):
|
|||||||
while True:
|
while True:
|
||||||
line = _file.readline()
|
line = _file.readline()
|
||||||
if line:
|
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)
|
self._process_line(line)
|
||||||
else:
|
else:
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
@@ -69,7 +131,7 @@ class LogTrigger(Trigger):
|
|||||||
|
|
||||||
# Detect if file has been rotated
|
# Detect if file has been rotated
|
||||||
if os.stat(self.file).st_ino != current_inode:
|
if os.stat(self.file).st_ino != current_inode:
|
||||||
self.log.debug("File %s rolled over. Reopening...", self.file)
|
self.log.info("File %s rolled over. Reopening...", self.file)
|
||||||
break # Exit inner loop to reopen file
|
break # Exit inner loop to reopen file
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
self.log.debug("file not found: %s", self.file)
|
self.log.debug("file not found: %s", self.file)
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "cpu_governor_auto_adjust"
|
name = "cpu_governor_auto_adjust"
|
||||||
version = "0.2.2"
|
version = "0.2.5"
|
||||||
description = "This application has been developed to automatically change cpu governor based on certain triggers."
|
description = "This application has been developed to automatically change cpu governor based on certain triggers."
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Martin Reurekas", email = "martin@semrks.nl" }
|
{ name = "Martin Reurekas", email = "martin@semrks.nl" }
|
||||||
|
|||||||
Reference in New Issue
Block a user