From e37441966550e8db6b31c9fd6d7e1ac4e06257aa Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 13:19:31 -0500 Subject: [PATCH 01/14] Make required events a set --- .../tracetools_analysis/processor/__init__.py | 6 +++--- .../tracetools_analysis/processor/cpu_time.py | 8 ++++---- .../tracetools_analysis/processor/memory_usage.py | 14 +++++++------- .../tracetools_analysis/processor/profile.py | 7 ++++--- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/tracetools_analysis/tracetools_analysis/processor/__init__.py b/tracetools_analysis/tracetools_analysis/processor/__init__.py index 3ca4104..fd3466a 100644 --- a/tracetools_analysis/tracetools_analysis/processor/__init__.py +++ b/tracetools_analysis/tracetools_analysis/processor/__init__.py @@ -138,14 +138,14 @@ class EventHandler(Dependant): return None @staticmethod - def required_events() -> List[str]: + def required_events() -> Set[str]: """ - Get the list of events required by this EventHandler. + Get the set of events required by this EventHandler. Without these events, the EventHandler would be invalid/useless. Inheriting classes can decide not to declare that they require specific events. """ - return [] + return {} def register_processor(self, processor: 'Processor') -> None: """Register processor with this `EventHandler` so that it can query other handlers.""" diff --git a/tracetools_analysis/tracetools_analysis/processor/cpu_time.py b/tracetools_analysis/tracetools_analysis/processor/cpu_time.py index d842ac3..ea77954 100644 --- a/tracetools_analysis/tracetools_analysis/processor/cpu_time.py +++ b/tracetools_analysis/tracetools_analysis/processor/cpu_time.py @@ -15,7 +15,7 @@ """Module for CPU time events processing.""" from typing import Dict -from typing import List +from typing import Set from tracetools_read import get_field @@ -53,10 +53,10 @@ class CpuTimeHandler(EventHandler): self._cpu_start: Dict[int, int] = {} @staticmethod - def required_events() -> List[str]: - return [ + def required_events() -> Set[str]: + return { 'sched_switch', - ] + } @property def data(self) -> CpuTimeDataModel: diff --git a/tracetools_analysis/tracetools_analysis/processor/memory_usage.py b/tracetools_analysis/tracetools_analysis/processor/memory_usage.py index 4d2b1ee..a7a30b5 100644 --- a/tracetools_analysis/tracetools_analysis/processor/memory_usage.py +++ b/tracetools_analysis/tracetools_analysis/processor/memory_usage.py @@ -15,7 +15,7 @@ """Module for memory usage events processing.""" from typing import Dict -from typing import List +from typing import Set from tracetools_read import get_field @@ -98,11 +98,11 @@ class UserspaceMemoryUsageHandler(MemoryUsageHandler): self._memory: Dict[int, int] = {} @staticmethod - def required_events() -> List[str]: - return [ + def required_events() -> Set[str]: + return { 'lttng_ust_libc:malloc', 'lttng_ust_libc:free', - ] + } def _handle_malloc( self, event: Dict, metadata: EventMetadata @@ -209,11 +209,11 @@ class KernelMemoryUsageHandler(MemoryUsageHandler): ) @staticmethod - def required_events() -> List[str]: - return [ + def required_events() -> Set[str]: + return { 'kmem_mm_page_alloc', 'kmem_mm_page_free', - ] + } def _handle_malloc( self, event: Dict, metadata: EventMetadata diff --git a/tracetools_analysis/tracetools_analysis/processor/profile.py b/tracetools_analysis/tracetools_analysis/processor/profile.py index 1ed4b1a..2f08611 100644 --- a/tracetools_analysis/tracetools_analysis/processor/profile.py +++ b/tracetools_analysis/tracetools_analysis/processor/profile.py @@ -17,6 +17,7 @@ from collections import defaultdict from typing import Dict from typing import List +from typing import Set from typing import Union from tracetools_read import get_field @@ -87,12 +88,12 @@ class ProfileHandler(EventHandler): self._current_funcs: Dict[int, List[List[Union[str, int]]]] = defaultdict(list) @staticmethod - def required_events() -> List[str]: - return [ + def required_events() -> Set[str]: + return { 'lttng_ust_cyg_profile_fast:func_entry', 'lttng_ust_cyg_profile_fast:func_exit', 'sched_switch', - ] + } @staticmethod def addr_to_int(addr: Union[int, str]) -> int: From 6412798a47bd6d8074a63391caaee2462e377b2a Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 13:19:59 -0500 Subject: [PATCH 02/14] Define a (single) required event for Ros2Handler --- tracetools_analysis/tracetools_analysis/processor/ros2.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tracetools_analysis/tracetools_analysis/processor/ros2.py b/tracetools_analysis/tracetools_analysis/processor/ros2.py index fb8dc9d..dedc08d 100644 --- a/tracetools_analysis/tracetools_analysis/processor/ros2.py +++ b/tracetools_analysis/tracetools_analysis/processor/ros2.py @@ -15,6 +15,7 @@ """Module for trace events processor and ROS model creation.""" from typing import Dict +from typing import Set from tracetools_read import get_field @@ -76,6 +77,12 @@ class Ros2Handler(EventHandler): # Temporary buffers self._callback_instances = {} + @staticmethod + def required_events() -> Set[str]: + return { + 'ros2:rcl_init', + } + @property def data(self) -> Ros2DataModel: return self._data_model From 0ceea3025c4cdbd8a0f37a9cd59901bc122bc817 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 13:20:22 -0500 Subject: [PATCH 03/14] Extract get_event_names() method --- .../tracetools_analysis/processor/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tracetools_analysis/tracetools_analysis/processor/__init__.py b/tracetools_analysis/tracetools_analysis/processor/__init__.py index fd3466a..64c6d27 100644 --- a/tracetools_analysis/tracetools_analysis/processor/__init__.py +++ b/tracetools_analysis/tracetools_analysis/processor/__init__.py @@ -321,11 +321,18 @@ class Processor(): for handler in handlers: handler.register_processor(self) + @staticmethod + def get_event_names( + events: List[DictEvent], + ) -> Set[str]: + """Get set of names from a list of events.""" + return {get_event_name(event) for event in events} + def _check_required_events( self, events: List[DictEvent], ) -> None: - event_names = {get_event_name(event) for event in events} + event_names = self.get_event_names(events) # Check names separately so that we can know which event from which handler is missing for handler in self._expanded_handlers: for name in handler.required_events(): From b9e15aac41be1faa43ec50f74e5c21fe978b761e Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 13:20:56 -0500 Subject: [PATCH 04/14] Make sure Processor is is given at least one EventHandler --- tracetools_analysis/tracetools_analysis/processor/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tracetools_analysis/tracetools_analysis/processor/__init__.py b/tracetools_analysis/tracetools_analysis/processor/__init__.py index 64c6d27..83b348d 100644 --- a/tracetools_analysis/tracetools_analysis/processor/__init__.py +++ b/tracetools_analysis/tracetools_analysis/processor/__init__.py @@ -272,6 +272,8 @@ class Processor(): :param kwargs: the parameters to pass on to new handlers """ self._initial_handlers = list(handlers) + if len(self._initial_handlers) == 0: + raise RuntimeError('Must provide at least one handler!') self._expanded_handlers = self._expand_dependencies(*handlers, **kwargs) self._handler_multimap = self._get_handler_maps(self._expanded_handlers) self._register_with_handlers(self._expanded_handlers) From acce73f26d69575135e998abd35c1a7db7297b98 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 13:52:25 -0500 Subject: [PATCH 05/14] Raise error if MemoryUsageHandler is instanciated directly --- .../tracetools_analysis/processor/memory_usage.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tracetools_analysis/tracetools_analysis/processor/memory_usage.py b/tracetools_analysis/tracetools_analysis/processor/memory_usage.py index a7a30b5..dbc9684 100644 --- a/tracetools_analysis/tracetools_analysis/processor/memory_usage.py +++ b/tracetools_analysis/tracetools_analysis/processor/memory_usage.py @@ -31,6 +31,8 @@ class MemoryUsageHandler(EventHandler): self, **kwargs, ) -> None: + if type(self) is MemoryUsageHandler: + raise RuntimeError('Do not instanciate MemoryUsageHandler directly!') super().__init__(**kwargs) self._data_model = MemoryUsageDataModel() From db9e94b1dc6875070693a06478dc3e449458b2e5 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 13:52:44 -0500 Subject: [PATCH 06/14] Add AutoProcessor --- .../tracetools_analysis/processor/__init__.py | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/tracetools_analysis/tracetools_analysis/processor/__init__.py b/tracetools_analysis/tracetools_analysis/processor/__init__.py index 83b348d..6b37e3e 100644 --- a/tracetools_analysis/tracetools_analysis/processor/__init__.py +++ b/tracetools_analysis/tracetools_analysis/processor/__init__.py @@ -405,6 +405,116 @@ class Processor(): handler.data.print_data() +class AutoProcessor(): + """ + Automatic processor, which takes a list of events and enables all relevant handlers. + + It checks each existing EventHandler, and, if its required events are in the events list, it + uses that handler. + """ + + def __init__( + self, + events: List[DictEvent], + **kwargs, + ) -> None: + """ + Create an AutoProcessor. + + :param events: the list of events to process + :param kwargs: the kwargs to provide when instanciating EventHandler subclasses + """ + handlers = self.get_applicable_event_handlers(events) + processor = Processor( + *handlers, + **kwargs, + ) + processor.process(events) + + @staticmethod + def get_applicable_event_handlers( + events: List[DictEvent], + ) -> List[EventHandler]: + """ + Get applicable EventHandler instances for a list of events. + + :param events: the list of events + :return the concrete EventHandler instances which are applicable + """ + event_names = Processor.get_event_names(events) + # Force import of all processor submodules (i.e. files) so that we can find all + # EventHandler subclasses + AutoProcessor._import_event_handler_submodules() + all_handler_classes = AutoProcessor._get_subclasses(EventHandler) + applicable_handler_classes = AutoProcessor._get_applicable_event_handler_classes( + event_names, + all_handler_classes, + ) + return AutoProcessor._get_event_handler_instances(applicable_handler_classes) + + @staticmethod + def _get_applicable_event_handler_classes( + event_names: List[str], + handler_classes: List[Type[EventHandler]], + ) -> List[Type[EventHandler]]: + """ + Get applicable EventHandler subclasses for a list of event names. + + :param event_names: the list of event names + :return: a list of EventHandler subclasses for which requirements are met + """ + return [ + handler for handler in handler_classes + if set(handler.required_events()).issubset(event_names) + ] + + @staticmethod + def _get_event_handler_instances( + handler_classes: List[Type[EventHandler]], + **kwargs, + ) -> List[EventHandler]: + """ + Create instances from a list of EventHandlers (sub)classes. + + :param handler_classes: the list of EventHandler subclasses + :param kwargs: the kwargs to provide when instanciating EventHandler subclasses + :return: the list of concrete instances + """ + # Doing this manually to catch exceptions, e.g. when a given EventHandler subclass is + # abstract and thus should not be instanciated + handlers = [] + for handler_class in handler_classes: + try: + instance = handler_class(**kwargs) + handlers.append(instance) + except: + pass + return handlers + + @staticmethod + def _get_subclasses( + cls: Type, + ) -> Set[Type]: + """Get all subclasses of a class recursively.""" + return set(cls.__subclasses__()) | { + subsubcls for subcls in cls.__subclasses__() for subsubcls in AutoProcessor._get_subclasses(subcls) + } + + @staticmethod + def _import_event_handler_submodules(recursive=True): + """Force import of EventHandler submodules.""" + import importlib + import pkgutil + package = importlib.import_module(__name__) + results = {} + for loader, name, is_pkg in pkgutil.walk_packages(package.__path__): + full_name = package.__name__ + '.' + name + results[full_name] = importlib.import_module(full_name) + if recursive and is_pkg: + results.update(_import_event_handler_submodules(full_name)) + return results + + class ProcessingProgressDisplay(): """Display processing progress periodically on stdout.""" From 154ad1af7da97ccbb95e30f93b26b78f5df82999 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 13:53:30 -0500 Subject: [PATCH 07/14] Add script entrypoint that uses AutoProcessor --- tracetools_analysis/setup.py | 1 + .../tracetools_analysis/scripts/auto.py | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tracetools_analysis/tracetools_analysis/scripts/auto.py diff --git a/tracetools_analysis/setup.py b/tracetools_analysis/setup.py index 840a341..390b7c1 100644 --- a/tracetools_analysis/setup.py +++ b/tracetools_analysis/setup.py @@ -43,6 +43,7 @@ setup( 'console_scripts': [ f'convert = {package_name}.convert:main', f'process = {package_name}.process:main', + f'auto = {package_name}.scripts.auto:main', f'cb_durations = {package_name}.scripts.cb_durations:main', f'memory_usage = {package_name}.scripts.memory_usage:main', ], diff --git a/tracetools_analysis/tracetools_analysis/scripts/auto.py b/tracetools_analysis/tracetools_analysis/scripts/auto.py new file mode 100644 index 0000000..797c465 --- /dev/null +++ b/tracetools_analysis/tracetools_analysis/scripts/auto.py @@ -0,0 +1,25 @@ +# Copyright 2019 Apex.AI, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from tracetools_analysis.loading import load_file +from tracetools_analysis.processor import AutoProcessor + +from . import get_input_path + + +def main(): + input_path = get_input_path() + + events = load_file(input_path) + processor = AutoProcessor(events) From 96fac6b7371020c4c9ba329cf73ec1b8b2628a2c Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 13:58:25 -0500 Subject: [PATCH 08/14] Fix linter errors --- .../tracetools_analysis/processor/__init__.py | 13 +++++++------ .../tracetools_analysis/scripts/auto.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tracetools_analysis/tracetools_analysis/processor/__init__.py b/tracetools_analysis/tracetools_analysis/processor/__init__.py index 6b37e3e..e70868f 100644 --- a/tracetools_analysis/tracetools_analysis/processor/__init__.py +++ b/tracetools_analysis/tracetools_analysis/processor/__init__.py @@ -425,11 +425,10 @@ class AutoProcessor(): :param kwargs: the kwargs to provide when instanciating EventHandler subclasses """ handlers = self.get_applicable_event_handlers(events) - processor = Processor( + Processor( *handlers, **kwargs, - ) - processor.process(events) + ).process(events) @staticmethod def get_applicable_event_handlers( @@ -487,7 +486,7 @@ class AutoProcessor(): try: instance = handler_class(**kwargs) handlers.append(instance) - except: + except RuntimeError: pass return handlers @@ -497,7 +496,9 @@ class AutoProcessor(): ) -> Set[Type]: """Get all subclasses of a class recursively.""" return set(cls.__subclasses__()) | { - subsubcls for subcls in cls.__subclasses__() for subsubcls in AutoProcessor._get_subclasses(subcls) + subsubcls + for subcls in cls.__subclasses__() + for subsubcls in AutoProcessor._get_subclasses(subcls) } @staticmethod @@ -511,7 +512,7 @@ class AutoProcessor(): full_name = package.__name__ + '.' + name results[full_name] = importlib.import_module(full_name) if recursive and is_pkg: - results.update(_import_event_handler_submodules(full_name)) + results.update(AutoProcessor._import_event_handler_submodules(full_name)) return results diff --git a/tracetools_analysis/tracetools_analysis/scripts/auto.py b/tracetools_analysis/tracetools_analysis/scripts/auto.py index 797c465..ed7ae0c 100644 --- a/tracetools_analysis/tracetools_analysis/scripts/auto.py +++ b/tracetools_analysis/tracetools_analysis/scripts/auto.py @@ -22,4 +22,4 @@ def main(): input_path = get_input_path() events = load_file(input_path) - processor = AutoProcessor(events) + AutoProcessor(events) From 1dc42cb4f6ac4ccd6202cc0b4d4d578b3b9a3b2c Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 14:04:30 -0500 Subject: [PATCH 09/14] Fix misspelling --- tracetools_analysis/tracetools_analysis/processor/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracetools_analysis/tracetools_analysis/processor/__init__.py b/tracetools_analysis/tracetools_analysis/processor/__init__.py index e70868f..29b1ff2 100644 --- a/tracetools_analysis/tracetools_analysis/processor/__init__.py +++ b/tracetools_analysis/tracetools_analysis/processor/__init__.py @@ -480,7 +480,7 @@ class AutoProcessor(): :return: the list of concrete instances """ # Doing this manually to catch exceptions, e.g. when a given EventHandler subclass is - # abstract and thus should not be instanciated + # abstract and thus should not be instantiated handlers = [] for handler_class in handler_classes: try: From 845362f9e3e9f6545791d1727e98329bdaacabf0 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 15:08:05 -0500 Subject: [PATCH 10/14] Change class lists to sets --- .../tracetools_analysis/processor/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tracetools_analysis/tracetools_analysis/processor/__init__.py b/tracetools_analysis/tracetools_analysis/processor/__init__.py index 29b1ff2..36739ba 100644 --- a/tracetools_analysis/tracetools_analysis/processor/__init__.py +++ b/tracetools_analysis/tracetools_analysis/processor/__init__.py @@ -455,21 +455,21 @@ class AutoProcessor(): def _get_applicable_event_handler_classes( event_names: List[str], handler_classes: List[Type[EventHandler]], - ) -> List[Type[EventHandler]]: + ) -> Set[Type[EventHandler]]: """ Get applicable EventHandler subclasses for a list of event names. :param event_names: the list of event names :return: a list of EventHandler subclasses for which requirements are met """ - return [ + return { handler for handler in handler_classes if set(handler.required_events()).issubset(event_names) - ] + } @staticmethod def _get_event_handler_instances( - handler_classes: List[Type[EventHandler]], + handler_classes: Set[Type[EventHandler]], **kwargs, ) -> List[EventHandler]: """ From af2e0a2d419ae3a360e709f07841acf0ad05bc9e Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 15:08:45 -0500 Subject: [PATCH 11/14] Expose name parameter for submodule import util method --- .../tracetools_analysis/processor/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tracetools_analysis/tracetools_analysis/processor/__init__.py b/tracetools_analysis/tracetools_analysis/processor/__init__.py index 36739ba..556b10d 100644 --- a/tracetools_analysis/tracetools_analysis/processor/__init__.py +++ b/tracetools_analysis/tracetools_analysis/processor/__init__.py @@ -502,11 +502,14 @@ class AutoProcessor(): } @staticmethod - def _import_event_handler_submodules(recursive=True): + def _import_event_handler_submodules( + name: str = __name__, + recursive=True, + ): """Force import of EventHandler submodules.""" import importlib import pkgutil - package = importlib.import_module(__name__) + package = importlib.import_module(name) results = {} for loader, name, is_pkg in pkgutil.walk_packages(package.__path__): full_name = package.__name__ + '.' + name From 1e84b9d35ead252ba38b2f9e64452460df1b0e11 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 15:09:08 -0500 Subject: [PATCH 12/14] Keep handlers are attribute in AutoProcessor --- tracetools_analysis/tracetools_analysis/processor/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tracetools_analysis/tracetools_analysis/processor/__init__.py b/tracetools_analysis/tracetools_analysis/processor/__init__.py index 556b10d..cef2e23 100644 --- a/tracetools_analysis/tracetools_analysis/processor/__init__.py +++ b/tracetools_analysis/tracetools_analysis/processor/__init__.py @@ -424,9 +424,9 @@ class AutoProcessor(): :param events: the list of events to process :param kwargs: the kwargs to provide when instanciating EventHandler subclasses """ - handlers = self.get_applicable_event_handlers(events) + self.handlers = self.get_applicable_event_handlers(events) Processor( - *handlers, + *self.handlers, **kwargs, ).process(events) From b4cd9e2043ad20d5c7258d1690be098063a00505 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 15:09:35 -0500 Subject: [PATCH 13/14] Add AutoProcessor test --- .../tracetools_analysis/test_autoprocessor.py | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 tracetools_analysis/test/tracetools_analysis/test_autoprocessor.py diff --git a/tracetools_analysis/test/tracetools_analysis/test_autoprocessor.py b/tracetools_analysis/test/tracetools_analysis/test_autoprocessor.py new file mode 100644 index 0000000..4bd4263 --- /dev/null +++ b/tracetools_analysis/test/tracetools_analysis/test_autoprocessor.py @@ -0,0 +1,180 @@ +# Copyright 2019 Apex.AI, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict +from typing import List +import unittest + +from tracetools_analysis.processor import AutoProcessor +from tracetools_analysis.processor import EventHandler +from tracetools_analysis.processor import EventMetadata + + +class AbstractEventHandler(EventHandler): + + def __init__(self, **kwargs) -> None: + if type(self) is AbstractEventHandler: + raise RuntimeError() + super().__init__(**kwargs) + + +class SubSubEventHandler(AbstractEventHandler): + + def __init__(self) -> None: + handler_map = { + 'myeventname': self._handler_whatever, + 'myeventname69': self._handler_whatever, + } + super().__init__(handler_map=handler_map) + + @staticmethod + def required_events() -> List[str]: + return [ + 'myeventname', + 'myeventname69', + ] + + def _handler_whatever( + self, event: Dict, metadata: EventMetadata + ) -> None: + pass + + +class SubSubEventHandler2(AbstractEventHandler): + + def __init__(self) -> None: + handler_map = { + 'myeventname2': self._handler_whatever, + } + super().__init__(handler_map=handler_map) + + @staticmethod + def required_events() -> List[str]: + return [ + 'myeventname2', + ] + + def _handler_whatever( + self, event: Dict, metadata: EventMetadata + ) -> None: + pass + + +class SubEventHandler(EventHandler): + + def __init__(self) -> None: + handler_map = { + 'myeventname3': self._handler_whatever, + } + super().__init__(handler_map=handler_map) + + @staticmethod + def required_events() -> List[str]: + return [ + 'myeventname3', + ] + + def _handler_whatever( + self, event: Dict, metadata: EventMetadata + ) -> None: + pass + + +class TestAutoProcessor(unittest.TestCase): + + def __init__(self, *args) -> None: + super().__init__( + *args, + ) + + def test_separate_methods(self) -> None: + # Testing logic/methods separately, since we don't actually want to process + + # Getting subclasses + subclasses = AutoProcessor._get_subclasses(EventHandler) + # Will also contain the real classes + self.assertTrue( + all( + handler in subclasses + for handler in [ + AbstractEventHandler, + SubSubEventHandler, + SubSubEventHandler2, + SubEventHandler, + ] + ) + ) + + # Finding applicable classes + event_names = { + 'myeventname', + 'myeventname2', + 'myeventname3', + } + applicable_handler_classes = AutoProcessor._get_applicable_event_handler_classes( + event_names, + subclasses, + ) + self.assertTrue( + all( + handler in applicable_handler_classes + for handler in [ + AbstractEventHandler, + SubSubEventHandler2, + SubEventHandler, + ] + ) and + SubSubEventHandler not in applicable_handler_classes + ) + + # Creating instances + instances = AutoProcessor._get_event_handler_instances(applicable_handler_classes) + for instance in instances: + self.assertTrue(type(instance) is not AbstractEventHandler) + + def test_all(self) -> None: + # Test the main method with all the logic + events = [ + { + '_name': 'myeventname', + '_timestamp': 0, + 'cpu_id': 0, + }, + { + '_name': 'myeventname2', + '_timestamp': 69, + 'cpu_id': 0, + }, + { + '_name': 'myeventname3', + '_timestamp': 6969, + 'cpu_id': 0, + }, + ] + instances = AutoProcessor.get_applicable_event_handlers(events) + for instance in instances: + self.assertTrue(type(instance) is not AbstractEventHandler) + # Will also contain the real classes + self.assertEqual( + sum( + isinstance(instance, handler_class) + for handler_class in [SubEventHandler, SubSubEventHandler2] + for instance in instances + ), + 2, + ) + + +if __name__ == '__main__': + unittest.main() From b42fbbe1c6318e247ac35ee5699f3041ba469aa2 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 31 Dec 2019 15:15:08 -0500 Subject: [PATCH 14/14] Add method to make AutoProcessor print all data models --- .../tracetools_analysis/processor/__init__.py | 5 +++++ tracetools_analysis/tracetools_analysis/scripts/auto.py | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tracetools_analysis/tracetools_analysis/processor/__init__.py b/tracetools_analysis/tracetools_analysis/processor/__init__.py index cef2e23..5ae8540 100644 --- a/tracetools_analysis/tracetools_analysis/processor/__init__.py +++ b/tracetools_analysis/tracetools_analysis/processor/__init__.py @@ -430,6 +430,11 @@ class AutoProcessor(): **kwargs, ).process(events) + def print_data(self) -> None: + """Print data models of all handlers.""" + for handler in self.handlers: + handler.data.print_data() + @staticmethod def get_applicable_event_handlers( events: List[DictEvent], diff --git a/tracetools_analysis/tracetools_analysis/scripts/auto.py b/tracetools_analysis/tracetools_analysis/scripts/auto.py index ed7ae0c..091696a 100644 --- a/tracetools_analysis/tracetools_analysis/scripts/auto.py +++ b/tracetools_analysis/tracetools_analysis/scripts/auto.py @@ -22,4 +22,5 @@ def main(): input_path = get_input_path() events = load_file(input_path) - AutoProcessor(events) + processor = AutoProcessor(events) + processor.print_data()