diff --git a/analyzers/__init__.py b/analyzers/__init__.py index 0730b39..10b3609 100644 --- a/analyzers/__init__.py +++ b/analyzers/__init__.py @@ -1,15 +1,17 @@ from typing import List from .analyzer import Analyzer, Result -from .analyzer.biogames import BoardDurationAnalyzer, SimulationRoundsAnalyzer, ActivationSequenceAnalyzer -from .analyzer.default import LogEntryCountAnalyzer, LocationAnalyzer, LogEntrySequenceAnalyzer, ActionSequenceAnalyzer +from .analyzer.biogames import BoardDurationAnalyzer, SimulationRoundsAnalyzer, ActivationSequenceAnalyzer, \ + BiogamesCategorizer +from .analyzer.default import LogEntryCountAnalyzer, LocationAnalyzer, LogEntrySequenceAnalyzer, ActionSequenceAnalyzer, \ + CategorizerStub from .analyzer.locomotion import LocomotionActionAnalyzer, CacheSequenceAnalyzer from .analyzer.mask import MaskSpatials from .render import Render +from .render.biogames import SimulationRoundsRender, BoardDurationHistRender, BoardDurationBoxRender from .render.default import PrintRender, JSONRender, TrackRender, HeatMapRender from .render.locomotion import LocomotionActionRelativeRender, LocomotionActionAbsoluteRender, \ LocomotionActionRatioRender -from .render.biogames import SimulationRoundsRender, BoardDurationHistRender, BoardDurationBoxRender __FALLBACK__ = PrintRender __MAPPING__ = { diff --git a/analyzers/analyzer/__init__.py b/analyzers/analyzer/__init__.py index e24b7bf..bdfe6db 100644 --- a/analyzers/analyzer/__init__.py +++ b/analyzers/analyzer/__init__.py @@ -1,11 +1,14 @@ -from analyzers.settings import LogSettings import logging +from collections import KeysView +from typing import Type, Sized, Collection -log = logging.getLogger(__name__) +from analyzers.settings import LogSettings + +log: logging.Logger = logging.getLogger(__name__) class Result: - def __init__(self, analysis, result): + def __init__(self, analysis: Type, result: Sized): self.result = result self.__analysis__ = analysis log.debug("set" + str(len(self.result))) @@ -18,18 +21,70 @@ class Result: return self.result def __repr__(self): - return "" + return "" + + +class ResultStore: + """Store Results""" + + def __init__(self, store_entry: Type[Collection] = list, store_action: callable = list.append) -> None: + self.store = {} + self.category = None + self.entry: Type[Collection] = store_entry + self.action: callable = store_action + + def new_category(self, key) -> None: + self.category = key + if not key in self.store: + self.store[key] = self.entry() + + def add(self, entry: Result) -> None: + self.action(self.store[self.category], entry) + + def get_store(self) -> dict: + return dict(self.store) + + def get_all(self) -> list: + """ + Throw all categories together + :return: + """ + result = [] + for key in self.store: + result += self.store[key] + return result + + def get_categories(self) -> KeysView: + return self.store.keys() + + def get_category(self, key): + if key not in self.store: + return self.entry + return self.store[key] + + def serializable(self): + values = {} + for key in self.store: + values[key] = [{"analysis": str(result.analysis()), "result": result.get()} for result in self.store[key]] + return values class Analyzer: - def __init__(self, settings: LogSettings): - self.settings = settings + """Operate on log entries, one at a time""" + + def __init__(self, settings: LogSettings) -> None: + self.settings: LogSettings = settings def process(self, entry: dict) -> bool: + """ + Process an entry + :param entry: Entry to process + :return: True if consumed, False for further analysis + """ raise NotImplementedError() - def result(self) -> Result: + def result(self, store: ResultStore) -> None: raise NotImplementedError() - def name(self): + def name(self) -> str: return self.__name__ diff --git a/analyzers/analyzer/biogames.py b/analyzers/analyzer/biogames.py index d3db374..aa6960c 100644 --- a/analyzers/analyzer/biogames.py +++ b/analyzers/analyzer/biogames.py @@ -1,7 +1,8 @@ import logging from collections import defaultdict -from . import Result, LogSettings, Analyzer +from . import Result, LogSettings, Analyzer, ResultStore +from .default import CategorizerStub logger = logging.getLogger(__name__) @@ -15,7 +16,7 @@ class BoardDurationAnalyzer(Analyzer): def render(self) -> str: return "\n".join(["{}\t{}".format(entry["active"], entry["id"]) for entry in self.result().get()]) - def result(self) -> Result: + def result(self, store: ResultStore) -> None: result = [] last_timestamp = None last_board = None @@ -27,7 +28,7 @@ class BoardDurationAnalyzer(Analyzer): last_timestamp = timestamp last_board = board_id # TODO: last board? - return Result(type(self), result) + store.add(Result(type(self), result)) def process(self, entry: dict) -> bool: entry_type = entry[self.settings.type_field] @@ -54,8 +55,8 @@ class SimulationRoundsAnalyzer(Analyzer): super().__init__(settings) self.store = defaultdict(lambda: -1) # TODO verify - def result(self) -> Result: - return Result(type(self), dict(self.store)) + def result(self, store: ResultStore) -> None: + store.add(Result(type(self), dict(self.store))) def process(self, entry: dict) -> bool: entry_type = entry[self.settings.type_field] @@ -73,8 +74,8 @@ class ActivationSequenceAnalyzer(Analyzer): super().__init__(settings) self.store = [] - def result(self) -> Result: - return Result(type(self), self.store) + def result(self, store: ResultStore) -> None: + store.add(Result(type(self), self.store)) def process(self, entry: dict) -> bool: if entry[self.settings.type_field] in self.settings.sequences['start']: @@ -82,3 +83,15 @@ class ActivationSequenceAnalyzer(Analyzer): self.store.append(entry['cache']['@id']) else: logger.error("null cache") + return False + + +class BiogamesCategorizer(CategorizerStub): + def __init__(self, settings: LogSettings): + super().__init__(settings) + + def process(self, entry: dict) -> bool: + if self.key is "default": + if entry[self.settings.type_field] in self.settings.custom['instance_start']: + self.key = entry[self.settings.custom['instance_id']] + return False diff --git a/analyzers/analyzer/default.py b/analyzers/analyzer/default.py index 81203f5..66f5c5a 100644 --- a/analyzers/analyzer/default.py +++ b/analyzers/analyzer/default.py @@ -1,7 +1,7 @@ import logging from collections import defaultdict -from . import Result, LogSettings, Analyzer +from . import Result, LogSettings, Analyzer, ResultStore class LocationAnalyzer(Analyzer): @@ -15,14 +15,14 @@ class LocationAnalyzer(Analyzer): super().__init__(settings) self.entries = [] - def result(self) -> Result: + def result(self, store: ResultStore) -> None: self.log.debug(len(self.entries)) - return Result(type(self), list(self.entries)) + store.add(Result(type(self), list(self.entries))) def process(self, entry: dict) -> bool: if entry[self.settings.type_field] in self.settings.spatials: self.entries.append(entry) - #self.log.debug(len(self.entries)) + # self.log.debug(len(self.entries)) return False @@ -32,8 +32,8 @@ class LogEntryCountAnalyzer(Analyzer): """ __name__ = "LogEntryCount" - def result(self) -> Result: - return Result(type(self), dict(self.store)) + def result(self, store: ResultStore) -> None: + store.add(Result(type(self), dict(self.store))) def process(self, entry: dict) -> bool: self.store[entry[self.settings.type_field]] += 1 @@ -50,8 +50,8 @@ class LogEntrySequenceAnalyzer(Analyzer): """ __name__ = "LogEntrySequence" - def result(self) -> Result: - return Result(type(self), list(self.store)) + def result(self, store: ResultStore) -> None: + store.add(Result(type(self), list(self.store))) def process(self, entry: dict) -> bool: entry_type = entry[self.settings.type_field] @@ -75,3 +75,21 @@ class ActionSequenceAnalyzer(LogEntrySequenceAnalyzer): return False self.store.append(entry_type) return False + + +class CategorizerStub(Analyzer): + """ + generate a new Category in a ResultStore + """ + + def process(self, entry: dict) -> bool: + raise NotImplementedError() + + __name__ = "Categorizer" + + def result(self, store: ResultStore) -> None: + store.new_category(self.key) + + def __init__(self, settings: LogSettings): + super().__init__(settings) + self.key = "default" diff --git a/analyzers/analyzer/locomotion.py b/analyzers/analyzer/locomotion.py index ff33525..671680a 100644 --- a/analyzers/analyzer/locomotion.py +++ b/analyzers/analyzer/locomotion.py @@ -1,5 +1,5 @@ import util -from . import Analyzer, LogSettings, Result +from . import Analyzer, LogSettings, Result, ResultStore def init_filter(settings: LogSettings, state: str) -> callable: @@ -41,7 +41,7 @@ class LocomotionActionAnalyzer(Analyzer): self.current_cache = None self.last = None - def result(self) -> Result: + def result(self, store: ResultStore) -> None: if self.last is not None: if self.current_cache is None: self.locomotion.append(self.last - self.cache_time) @@ -51,7 +51,7 @@ class LocomotionActionAnalyzer(Analyzer): locomotion = sum(self.locomotion) action = sum(self.actions) total = locomotion + action - return Result(type(self), { + store.add(Result(type(self), { 'locomotion_sum': locomotion, 'action_sum': action, 'locomotion': self.locomotion, @@ -60,7 +60,7 @@ class LocomotionActionAnalyzer(Analyzer): 'locomotion_relative': locomotion / total, 'action_relative': action / total, 'locomotion_action_ratio': locomotion / action, - }) + })) def __init__(self, settings: LogSettings): super().__init__(settings) @@ -86,8 +86,8 @@ class CacheSequenceAnalyzer(Analyzer): self.store.append((entry['timestamp'], entry['cache'])) return False - def result(self) -> Result: - return Result(type(self), list(self.store)) + def result(self, store: ResultStore) -> None: + store.add(Result(type(self), list(self.store))) def __init__(self, settings: LogSettings): super().__init__(settings) diff --git a/analyzers/analyzer/mask.py b/analyzers/analyzer/mask.py index b4fc310..f7f5c89 100644 --- a/analyzers/analyzer/mask.py +++ b/analyzers/analyzer/mask.py @@ -1,4 +1,4 @@ -from . import Analyzer +from . import Analyzer, Result, ResultStore class MaskSpatials(Analyzer): @@ -11,5 +11,5 @@ class MaskSpatials(Analyzer): return True return False - def result(self) -> int: - return self.masked + def result(self, store: ResultStore) -> None: + store.add(Result(type(self), {"masked": self.masked})) diff --git a/biogames2.json b/biogames2.json index 921368b..2502b01 100644 --- a/biogames2.json +++ b/biogames2.json @@ -13,18 +13,22 @@ ], "analyzers": { "analyzers": [ - "LocationAnalyzer" + "BiogamesCategorizer", + "LocomotionActionAnalyzer", + "LogEntryCountAnalyzer" ] }, - "disabled_analyzers":[ - "LogEntryCountAnalyzer", - "LogEntrySequenceAnalyzer", - "ActionSequenceAnalyzer", - "BoardDurationAnalyzer", - "LocomotionActionAnalyzer", - "CacheSequenceAnalyzer", - "SimulationRoundsAnalyzer", - "ActivationSequenceAnalyzer"], + "disabled_analyzers": [ + "LocationAnalyzer", + "LogEntryCountAnalyzer", + "LogEntrySequenceAnalyzer", + "ActionSequenceAnalyzer", + "BoardDurationAnalyzer", + "LocomotionActionAnalyzer", + "CacheSequenceAnalyzer", + "SimulationRoundsAnalyzer", + "ActivationSequenceAnalyzer" + ], "sequences": { "start": "de.findevielfalt.games.game2.instance.log.entry.LogEntryCache", "end": { @@ -32,8 +36,14 @@ "action.@class": "de.findevielfalt.games.game2.instance.action.CacheEnableAction" } }, - "custom":{ - "simulation_rounds": ["de.findevielfalt.games.game2.instance.log.entry.LogEntryQuestion"], - "simu_data": ["de.findevielfalt.games.game2.instance.data.sequence.simulation.SimulationBoardData"] + "custom": { + "simulation_rounds": [ + "de.findevielfalt.games.game2.instance.log.entry.LogEntryQuestion" + ], + "simu_data": [ + "de.findevielfalt.games.game2.instance.data.sequence.simulation.SimulationBoardData" + ], + "instance_start": "de.findevielfalt.games.game2.instance.log.entry.LogEntryStartInstance", + "instance_id": "instance_id" } } \ No newline at end of file diff --git a/log_analyzer.py b/log_analyzer.py index b33f5e8..3c91d32 100644 --- a/log_analyzer.py +++ b/log_analyzer.py @@ -1,23 +1,25 @@ +import json import logging -from load import LOADERS from typing import List -from analyzers import get_renderer, Analyzer, render -from analyzers.settings import LogSettings, load_settings -import analyzers +import analyzers +from analyzers import get_renderer, Analyzer, render +from analyzers.analyzer import ResultStore +from analyzers.settings import LogSettings, load_settings +from load import LOADERS logging.basicConfig(format='%(levelname)s %(name)s:%(message)s', level=logging.DEBUG) -log = logging.getLogger(__name__) +log: logging.Logger = logging.getLogger(__name__) def process_log(log_id: str, settings: LogSettings) -> List[Analyzer]: - logfile = "data/inst_{id}.{format}".format(id=log_id, format=settings.log_format) + logfile: str = "data/inst_{id}.{format}".format(id=log_id, format=settings.log_format) loader = LOADERS[settings.log_format]() try: loader.load(logfile) except BaseException as e: raise RuntimeError(e) - analyzers = [] + analyzers: List[Analyzer] = [] log.debug("build analyzers") for analyzer in settings.analyzers: analyzers.append(analyzer(settings)) @@ -30,36 +32,37 @@ def process_log(log_id: str, settings: LogSettings) -> List[Analyzer]: if __name__ == '__main__': - settings = load_settings("biogames2.json") - log_ids = [ + settings: LogSettings = load_settings("biogames2.json") + log_ids: List[str] = [ "20d4244719404ffab0ca386c76e4b112", "56d9b64144ab44e7b90bf766f3be32e3", "dc2cdc28ca074715b905e4aa5badff10", "e32b16998440475b994ab46d481d3e0c", ] - log_ids = [ + log_ids: List[str] = [ "34fecf49dbaca3401d745fb467", - "44ea194de594cd8d63ac0314be", - "57c444470dbf88605433ca935c", - "78e0c545b594e82edfad55bd7f", - "91abfd4b31a5562b1c66be37d9", - "597b704fe9ace475316c345903", - "e01a684aa29dff9ddd9705edf8", - "fbf9d64ae0bdad0de7efa3eec6", - "fe1331481f85560681f86827ec", + # "44ea194de594cd8d63ac0314be", + # "57c444470dbf88605433ca935c", + # "78e0c545b594e82edfad55bd7f", + # "91abfd4b31a5562b1c66be37d9", + # "597b704fe9ace475316c345903", + # "e01a684aa29dff9ddd9705edf8", + # "fbf9d64ae0bdad0de7efa3eec6", + # "fe1331481f85560681f86827ec", "fec57041458e6cef98652df625", ] - results = [] - #TODO: capture session ID, dict + store: ResultStore = ResultStore() for log_id in log_ids: for analysis in process_log(log_id, settings): log.info("* Result for " + analysis.name()) # print(analysis.result()) # print(analysis.render()) - results.append(analysis.result()) + analysis.result(store) if False: for r in get_renderer(analyzers.LocomotionActionAnalyzer): - r().render(results) - render(analyzers.LocationAnalyzer, results) + r().render(store.get_all()) + if False: + render(analyzers.LocationAnalyzer, store.get_all()) + print(json.dumps(store.serializable(), indent=1)) # for analyzers in analyzers: # if analyzers.name() in ["LogEntryCount", "ActionSequenceAnalyzer"]: