Merge branch '43-add-ld_preload-feature-for-tracing' into 'master'

Resolve "Add LD_PRELOAD feature for tracing"

Closes #43

See merge request micro-ROS/ros_tracing/ros2_tracing!84
This commit is contained in:
Christophe Bedard 2019-08-13 09:20:46 +00:00
commit c07ac4edee
6 changed files with 202 additions and 7 deletions

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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()

View file

@ -14,11 +14,14 @@
"""Module for the Trace action."""
import os
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
@ -34,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_EVENT_PATTERN = '^lttng_ust_cyg_profile.*:func_.*'
def __init__(
self,
*,
@ -42,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:
"""
@ -49,9 +57,10 @@ 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
:param profile_fast: `True` to use fast profiling, `False` for normal (only if necessary)
"""
super().__init__(**kwargs)
if append_timestamp:
@ -60,12 +69,47 @@ 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
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:
"""Check if the UST events list contains at least one profiling event."""
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]:
"""
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
# <input_lib_name>: <full path, if found>
# 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]]:
# 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(
@ -79,9 +123,11 @@ 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)}), '
f'profiling={self.__ld_preload_action is not None}, '
f'profile_fast={self.__profile_fast}'
)