From 31aa5e674da7fe60d1647f05ed39d98d7431b85d Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Wed, 7 Aug 2019 09:53:43 +0200 Subject: [PATCH 1/8] Add basic test files --- tracetools_launch/test/test_copyright.py | 23 +++++++++++++++++++++++ tracetools_launch/test/test_flake8.py | 23 +++++++++++++++++++++++ tracetools_launch/test/test_pep257.py | 23 +++++++++++++++++++++++ tracetools_launch/test/test_xmllint.py | 23 +++++++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 tracetools_launch/test/test_copyright.py create mode 100644 tracetools_launch/test/test_flake8.py create mode 100644 tracetools_launch/test/test_pep257.py create mode 100644 tracetools_launch/test/test_xmllint.py diff --git a/tracetools_launch/test/test_copyright.py b/tracetools_launch/test/test_copyright.py new file mode 100644 index 0000000..cf0fae3 --- /dev/null +++ b/tracetools_launch/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2017 Open Source Robotics Foundation, 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 ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/tracetools_launch/test/test_flake8.py b/tracetools_launch/test/test_flake8.py new file mode 100644 index 0000000..eff8299 --- /dev/null +++ b/tracetools_launch/test/test_flake8.py @@ -0,0 +1,23 @@ +# Copyright 2017 Open Source Robotics Foundation, 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 ament_flake8.main import main +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc = main(argv=[]) + assert rc == 0, 'Found errors' diff --git a/tracetools_launch/test/test_pep257.py b/tracetools_launch/test/test_pep257.py new file mode 100644 index 0000000..0e38a6c --- /dev/null +++ b/tracetools_launch/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2017 Open Source Robotics Foundation, 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 ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=[]) + assert rc == 0, 'Found code style errors / warnings' diff --git a/tracetools_launch/test/test_xmllint.py b/tracetools_launch/test/test_xmllint.py new file mode 100644 index 0000000..f46285e --- /dev/null +++ b/tracetools_launch/test/test_xmllint.py @@ -0,0 +1,23 @@ +# Copyright 2019 Open Source Robotics Foundation, 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 ament_xmllint.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.xmllint +def test_xmllint(): + rc = main(argv=[]) + assert rc == 0, 'Found errors' From 6ded4f38457299c1d6527ab50d5a82af93e328f9 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Wed, 7 Aug 2019 09:57:27 +0200 Subject: [PATCH 2/8] Fix linter errors --- tracetools_launch/tracetools_launch/action.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tracetools_launch/tracetools_launch/action.py b/tracetools_launch/tracetools_launch/action.py index 8ec3419..3a1205b 100644 --- a/tracetools_launch/tracetools_launch/action.py +++ b/tracetools_launch/tracetools_launch/action.py @@ -14,7 +14,6 @@ """Module for the Trace action.""" -import os from typing import List from typing import Optional @@ -49,7 +48,7 @@ class Trace(Action): :param session_name: the name of the tracing session :param append_timestamp: whether to append timestamp to the session name - :param base_path: the path to the base directory in which to create the tracing session directory + :param base_path: the path to the base directory in which to create the session directory :param events_ust: the list of ROS UST events to enable :param events_kernel: the list of kernel events to enable """ @@ -79,9 +78,9 @@ class Trace(Action): def __repr__(self): return ( - "Trace(" - f"session_name='{self.__session_name}', " - f"base_path='{self.__base_path}', " - f"num_events_ust={len(self.__events_ust)}, " - f"num_events_kernel={len(self.__events_kernel)})" + 'Trace(' + f'session_name={self.__session_name}, ' + f'base_path={self.__base_path}, ' + f'num_events_ust={len(self.__events_ust)}, ' + f'num_events_kernel={len(self.__events_kernel)})' ) From 11d6f5c9c12f3288e206295eb013bdd6f54292b1 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Wed, 7 Aug 2019 10:00:59 +0200 Subject: [PATCH 3/8] Add initial support for LD_PRELOAD in Trace action --- tracetools_launch/tracetools_launch/action.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tracetools_launch/tracetools_launch/action.py b/tracetools_launch/tracetools_launch/action.py index 3a1205b..d7e69f2 100644 --- a/tracetools_launch/tracetools_launch/action.py +++ b/tracetools_launch/tracetools_launch/action.py @@ -14,10 +14,14 @@ """Module for the Trace action.""" +import re +import subprocess from typing import List from typing import Optional +from typing import Union from launch.action import Action +from launch.actions import SetEnvironmentVariable from launch.event import Event from launch.event_handlers import OnShutdown from launch.launch_context import LaunchContext @@ -33,6 +37,10 @@ class Trace(Action): Sets up and enables tracing through a launch file description. """ + PROFILE_LIB_NORMAL = 'liblttng-ust-cyg-profile.so' + PROFILE_LIB_FAST = 'liblttng-ust-cyg-profile-fast.so' + PROFILE_PATTERN = '^lttng_ust_cyg_profile.*:func_.*' + def __init__( self, *, @@ -41,6 +49,7 @@ class Trace(Action): base_path: str = path.DEFAULT_BASE_PATH, events_ust: List[str] = names.DEFAULT_EVENTS_ROS, events_kernel: List[str] = names.DEFAULT_EVENTS_KERNEL, + profile_fast: bool = True, **kwargs, ) -> None: """ @@ -51,6 +60,7 @@ class Trace(Action): :param base_path: the path to the base directory in which to create the session directory :param events_ust: the list of ROS UST events to enable :param events_kernel: the list of kernel events to enable + :param profile_fast: `True` to use fast profiling, `False` for normal (only if necessary) """ super().__init__(**kwargs) if append_timestamp: @@ -59,8 +69,41 @@ class Trace(Action): self.__base_path = base_path self.__events_ust = events_ust self.__events_kernel = events_kernel + self.__ld_preload_action = None + if self.has_profiling_events(events_ust): + profile_lib_name = self.PROFILE_LIB_FAST if profile_fast else self.PROFILE_LIB_NORMAL + self.__ld_preload_action = SetEnvironmentVariable( + 'LD_PRELOAD', + self.get_shared_lib_path(profile_lib_name), + ) + + @classmethod + def has_profiling_events(cls, events_ust: List[str]) -> bool: + matches = [re.match(cls.PROFILE_PATTERN, event_name) for event_name in events_ust] + return any(matches) + + @staticmethod + def get_shared_lib_path(lib_name: str) -> Union[str, None]: + """ + Get the full path to a given shared lib, if possible. + + :param lib_name: the name of the shared library + :return: the full path if found, `None` otherwise + """ + (exit_code, output) = subprocess.getstatusoutput(f'whereis -b {lib_name}') + if exit_code != 0: + return None + # Output of whereis is + # : + # Filter out empty strings, in case lib is not found + output_split = [split_part for split_part in output.split(':') if len(split_part) > 0] + if len(output_split) != 2: + return None + return output_split[1].strip() def execute(self, context: LaunchContext) -> Optional[List[Action]]: + if self.__ld_preload_action is not None: + context.add_action(self.__ld_preload_action) # TODO make sure this is done as late as possible context.register_event_handler(OnShutdown(on_shutdown=self._destroy)) # TODO make sure this is done as early as possible From cb3d22bf2910ed2939b6bed1adf93133bada1ba0 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Wed, 7 Aug 2019 10:01:09 +0200 Subject: [PATCH 4/8] Add tests for Trace action utils --- .../tracetools_launch/test_trace_action.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tracetools_launch/test/tracetools_launch/test_trace_action.py diff --git a/tracetools_launch/test/tracetools_launch/test_trace_action.py b/tracetools_launch/test/tracetools_launch/test_trace_action.py new file mode 100644 index 0000000..9f27407 --- /dev/null +++ b/tracetools_launch/test/tracetools_launch/test_trace_action.py @@ -0,0 +1,57 @@ +# Copyright 2019 Robert Bosch GmbH +# +# 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. + +import unittest + +from tracetools_launch.action import Trace + + +class TestTraceAction(unittest.TestCase): + + def __init__(self, *args) -> None: + super().__init__( + *args, + ) + + def test_has_profiling_events(self) -> None: + events_lists_match = [ + [ + 'lttng_ust_cyg_profile_fast:func_entry', + 'hashtag:yopo', + ], + [ + 'lttng_ust_cyg_profile:func_entry', + 'some_other_event', + 'lttng_ust_cyg_profile:func_exit', + ], + ] + events_lists_no_match = [ + [ + 'lttng_ust_statedump:bin_info', + 'ros2:event', + ], + [] + ] + for events in events_lists_match: + self.assertTrue(Trace.has_profiling_events(events)) + for events in events_lists_no_match: + self.assertFalse(Trace.has_profiling_events(events)) + + def test_get_shared_lib_path(self) -> None: + # Only test not finding a lib for now + self.assertIsNone(Trace.get_shared_lib_path('random_lib_that_does_not_exist_I_hope.so')) + + +if __name__ == '__main__': + unittest.main() From f5a6b03b918e4082a2bed8b0848d2769920a975e Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Wed, 7 Aug 2019 10:16:43 +0200 Subject: [PATCH 5/8] Fix use of LD_PRELOAD action --- tracetools_launch/tracetools_launch/action.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tracetools_launch/tracetools_launch/action.py b/tracetools_launch/tracetools_launch/action.py index d7e69f2..46b1dcb 100644 --- a/tracetools_launch/tracetools_launch/action.py +++ b/tracetools_launch/tracetools_launch/action.py @@ -102,12 +102,13 @@ class Trace(Action): return output_split[1].strip() def execute(self, context: LaunchContext) -> Optional[List[Action]]: - if self.__ld_preload_action is not None: - context.add_action(self.__ld_preload_action) # TODO make sure this is done as late as possible context.register_event_handler(OnShutdown(on_shutdown=self._destroy)) # TODO make sure this is done as early as possible self._setup() + if self.__ld_preload_action is not None: + return [self.__ld_preload_action] + return None def _setup(self) -> None: lttng.lttng_init( From 5812adce63b4d528ead17410b9b9f1549e0e2ac9 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Wed, 7 Aug 2019 10:18:34 +0200 Subject: [PATCH 6/8] Add docstring and rename constant --- tracetools_launch/tracetools_launch/action.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tracetools_launch/tracetools_launch/action.py b/tracetools_launch/tracetools_launch/action.py index 46b1dcb..b103d93 100644 --- a/tracetools_launch/tracetools_launch/action.py +++ b/tracetools_launch/tracetools_launch/action.py @@ -39,7 +39,7 @@ class Trace(Action): PROFILE_LIB_NORMAL = 'liblttng-ust-cyg-profile.so' PROFILE_LIB_FAST = 'liblttng-ust-cyg-profile-fast.so' - PROFILE_PATTERN = '^lttng_ust_cyg_profile.*:func_.*' + PROFILE_EVENT_PATTERN = '^lttng_ust_cyg_profile.*:func_.*' def __init__( self, @@ -79,7 +79,8 @@ class Trace(Action): @classmethod def has_profiling_events(cls, events_ust: List[str]) -> bool: - matches = [re.match(cls.PROFILE_PATTERN, event_name) for event_name in events_ust] + """Check if the UST events list contains at least one profiling event.""" + matches = [re.match(cls.PROFILE_EVENT_PATTERN, event_name) for event_name in events_ust] return any(matches) @staticmethod From 617a53e196fb3961409114f80cf9c16451a85a2b Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Wed, 7 Aug 2019 10:25:27 +0200 Subject: [PATCH 7/8] Update Trace action __repr__ --- tracetools_launch/tracetools_launch/action.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tracetools_launch/tracetools_launch/action.py b/tracetools_launch/tracetools_launch/action.py index b103d93..315d457 100644 --- a/tracetools_launch/tracetools_launch/action.py +++ b/tracetools_launch/tracetools_launch/action.py @@ -69,6 +69,7 @@ class Trace(Action): self.__base_path = base_path self.__events_ust = events_ust self.__events_kernel = events_kernel + self.__profile_fast = profile_fast self.__ld_preload_action = None if self.has_profiling_events(events_ust): profile_lib_name = self.PROFILE_LIB_FAST if profile_fast else self.PROFILE_LIB_NORMAL @@ -127,5 +128,7 @@ class Trace(Action): f'session_name={self.__session_name}, ' f'base_path={self.__base_path}, ' f'num_events_ust={len(self.__events_ust)}, ' - f'num_events_kernel={len(self.__events_kernel)})' + f'num_events_kernel={len(self.__events_kernel)}), ' + f'profiling={self.__ld_preload_action is not None}, ' + f'profile_fast={self.__profile_fast}' ) From 0826959984538485b4cc7a390b888f366e1d00ec Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 13 Aug 2019 10:05:44 +0200 Subject: [PATCH 8/8] Convert list comprehension to generator when using any() --- tracetools_launch/tracetools_launch/action.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tracetools_launch/tracetools_launch/action.py b/tracetools_launch/tracetools_launch/action.py index 315d457..ce46a80 100644 --- a/tracetools_launch/tracetools_launch/action.py +++ b/tracetools_launch/tracetools_launch/action.py @@ -81,8 +81,7 @@ class Trace(Action): @classmethod def has_profiling_events(cls, events_ust: List[str]) -> bool: """Check if the UST events list contains at least one profiling event.""" - matches = [re.match(cls.PROFILE_EVENT_PATTERN, event_name) for event_name in events_ust] - return any(matches) + return any(re.match(cls.PROFILE_EVENT_PATTERN, event_name) for event_name in events_ust) @staticmethod def get_shared_lib_path(lib_name: str) -> Union[str, None]: