diff --git a/ros2trace/.gitignore b/ros2trace/.gitignore new file mode 100644 index 0000000..eef29c1 --- /dev/null +++ b/ros2trace/.gitignore @@ -0,0 +1,3 @@ +*~ +*.pyc + diff --git a/ros2trace/package.xml b/ros2trace/package.xml new file mode 100644 index 0000000..096a8b0 --- /dev/null +++ b/ros2trace/package.xml @@ -0,0 +1,26 @@ + + + + ros2trace + 0.0.1 + + The trace command for ROS 2 command line tools. + + Christophe Bedard + Ingo Lütkebohle + Apache 2.0 + Christophe Bedard + + ros2cli + tracetools_trace + + ament_copyright + ament_flake8 + ament_pep257 + ament_xmllint + python3-pytest + + + ament_python + + diff --git a/ros2trace/ros2trace/__init__.py b/ros2trace/ros2trace/__init__.py new file mode 100644 index 0000000..4b18865 --- /dev/null +++ b/ros2trace/ros2trace/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/ros2trace/ros2trace/api/__init__.py b/ros2trace/ros2trace/api/__init__.py new file mode 100644 index 0000000..d39da83 --- /dev/null +++ b/ros2trace/ros2trace/api/__init__.py @@ -0,0 +1,68 @@ +# 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. + +"""API functions for the ROS 2 trace command.""" + +import os + +from tracetools_trace.tools import args +from tracetools_trace.tools import lttng + + +def add_trace_arguments(parser): + args.add_arguments(parser) + + +def init(args): + """ + Init and start tracing. + + :param args: the parsed arguments object containing the right fields + """ + session_name = args.session_name + base_path = args.path + full_path = os.path.join(base_path, session_name) + ros_events = args.events_ust + kernel_events = args.events_kernel + + ust_enabled = len(ros_events) > 0 + kernel_enabled = len(kernel_events) > 0 + if ust_enabled: + print(f'UST tracing enabled ({len(ros_events)} events)') + if args.list: + print(f'\tevents: {ros_events}') + else: + print('UST tracing disabled') + if kernel_enabled: + print(f'kernel tracing enabled ({len(kernel_events)} events)') + if args.list: + print(f'\tevents: {kernel_events}') + else: + print('kernel tracing disabled') + + print(f'writting tracing session to: {full_path}') + input('press enter to start...') + lttng.lttng_init(session_name, full_path, ros_events=ros_events, kernel_events=kernel_events) + + +def fini(args): + """ + Stop and finalize tracing. + + :param args: the parsed arguments object containing the right fields + """ + session_name = args.session_name + input('press enter to stop...') + print('stopping & destroying tracing session') + lttng.lttng_fini(session_name) diff --git a/ros2trace/ros2trace/command/__init__.py b/ros2trace/ros2trace/command/__init__.py new file mode 100644 index 0000000..4b18865 --- /dev/null +++ b/ros2trace/ros2trace/command/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/ros2trace/ros2trace/command/trace.py b/ros2trace/ros2trace/command/trace.py new file mode 100644 index 0000000..ee03363 --- /dev/null +++ b/ros2trace/ros2trace/command/trace.py @@ -0,0 +1,32 @@ +# 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. + +"""Module for trace command extension implementation.""" + +from ros2cli.command import CommandExtension +from ros2trace.api import add_trace_arguments +from ros2trace.api import init +from ros2trace.api import fini + + +class TraceCommand(CommandExtension): + """Trace ROS nodes to get information on their execution.""" + + def add_arguments(self, parser, cli_name): + add_trace_arguments(parser) + + def main(self, *, parser, args): + init(args) + fini(args) + return 0 diff --git a/ros2trace/setup.py b/ros2trace/setup.py new file mode 100644 index 0000000..4add34d --- /dev/null +++ b/ros2trace/setup.py @@ -0,0 +1,26 @@ +from setuptools import find_packages +from setuptools import setup + +setup( + name='ros2trace', + version='0.0.1', + packages=find_packages(exclude=['test']), + install_requires=['ros2cli'], + zip_safe=True, + maintainer='Christophe Bedard, Ingo Lütkebohle', + maintainer_email='fixed-term.christophe.bourquebedard@de.bosch.com, ingo.luetkebohle@de.bosch.com', + author='Christophe Bedard', + author_email='fixed-term.christophe.bourquebedard@de.bosch.com', + # url=', + keywords=[], + description='The run command for ROS 2 command line tools.', + long_description="""\ +The package provides the trace command for the ROS 2 command line tools.""", + license='Apache 2.0', + tests_require=['pytest'], + entry_points={ + 'ros2cli.command': [ + 'trace = ros2trace.command.trace:TraceCommand', + ], + } +) diff --git a/ros2trace/test/test_copyright.py b/ros2trace/test/test_copyright.py new file mode 100644 index 0000000..cf0fae3 --- /dev/null +++ b/ros2trace/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/ros2trace/test/test_flake8.py b/ros2trace/test/test_flake8.py new file mode 100644 index 0000000..eff8299 --- /dev/null +++ b/ros2trace/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/ros2trace/test/test_pep257.py b/ros2trace/test/test_pep257.py new file mode 100644 index 0000000..0e38a6c --- /dev/null +++ b/ros2trace/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/ros2trace/test/test_xmllint.py b/ros2trace/test/test_xmllint.py new file mode 100644 index 0000000..f46285e --- /dev/null +++ b/ros2trace/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' diff --git a/tracetools/package.xml b/tracetools/package.xml index 6bb4256..90ca10a 100644 --- a/tracetools/package.xml +++ b/tracetools/package.xml @@ -6,7 +6,7 @@ ROS 2 wrapper for instrumentation Christophe Bedard Ingo Luetkebohle - Apache Software License 2.0 + Apache 2.0 Ingo Luetkebohle Christophe Bedard diff --git a/tracetools_launch/.gitignore b/tracetools_launch/.gitignore new file mode 100644 index 0000000..eef29c1 --- /dev/null +++ b/tracetools_launch/.gitignore @@ -0,0 +1,3 @@ +*~ +*.pyc + diff --git a/tracetools_launch/launch/example.launch.py b/tracetools_launch/launch/example.launch.py new file mode 100644 index 0000000..325daa3 --- /dev/null +++ b/tracetools_launch/launch/example.launch.py @@ -0,0 +1,35 @@ +# 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. + +"""Example launch file for the Trace action.""" + +from launch import LaunchDescription +from launch_ros.actions import Node +from tracetools_launch.trace import Trace + + +def generate_launch_description(): + return LaunchDescription([ + Trace( + session_name='my-tracing-session', + base_path='/tmp'), + Node( + package='examples_rclcpp_minimal_publisher', + node_executable='publisher_member_function', + output='screen'), + Node( + package='examples_rclcpp_minimal_subscriber', + node_executable='subscriber_member_function', + output='screen'), + ]) diff --git a/tracetools_launch/package.xml b/tracetools_launch/package.xml new file mode 100644 index 0000000..c09d6f5 --- /dev/null +++ b/tracetools_launch/package.xml @@ -0,0 +1,24 @@ + + + + tracetools_launch + 0.0.1 + Launch integration for tracing + Christophe Bedard + Ingo Lütkebohle + Apache 2.0 + Christophe Bedard + + launch + launch_ros + tracetools_trace + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/tracetools_launch/setup.cfg b/tracetools_launch/setup.cfg new file mode 100644 index 0000000..b7ef5cb --- /dev/null +++ b/tracetools_launch/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script-dir=$base/lib/tracetools_launch +[install] +install-scripts=$base/lib/tracetools_launch diff --git a/tracetools_launch/setup.py b/tracetools_launch/setup.py new file mode 100644 index 0000000..f6c6015 --- /dev/null +++ b/tracetools_launch/setup.py @@ -0,0 +1,26 @@ +import glob + +from setuptools import find_packages +from setuptools import setup + +package_name = 'tracetools_launch' + +setup( + name=package_name, + version='0.0.1', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/' + package_name, ['package.xml']), + ('share/' + package_name + '/launch', glob.glob('launch/*.launch.py')), + ], + install_requires=['setuptools'], + maintainer='Christophe Bedard, Ingo Lütkebohle', + maintainer_email='fixed-term.christophe.bourquebedard@de.bosch.com, ingo.luetkebohle@de.bosch.com', + author='Christophe Bedard', + author_email='fixed-term.christophe.bourquebedard@de.bosch.com', + # url='', + keywords=['ROS'], + description='Launch integration for tracing', + license='Apache 2.0', + tests_require=['pytest'], +) diff --git a/tracetools_launch/tracetools_launch/__init__.py b/tracetools_launch/tracetools_launch/__init__.py new file mode 100644 index 0000000..4b18865 --- /dev/null +++ b/tracetools_launch/tracetools_launch/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/tracetools_launch/tracetools_launch/trace.py b/tracetools_launch/tracetools_launch/trace.py new file mode 100644 index 0000000..e46ea28 --- /dev/null +++ b/tracetools_launch/tracetools_launch/trace.py @@ -0,0 +1,82 @@ +# 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. + +"""Module for the Trace action.""" + +import os +from typing import List +from typing import Optional + +from launch.action import Action +from launch.event import Event +from launch.event_handlers import OnShutdown +from launch.launch_context import LaunchContext +from tracetools_trace.tools import lttng +from tracetools_trace.tools import names + + +class Trace(Action): + """ + Tracing action for launch. + + Sets up and enables tracing through a launch file description. + """ + + def __init__( + self, + *, + session_name: str, + base_path: str = '/tmp', + events_ust: List[str] = names.DEFAULT_EVENTS_ROS, + events_kernel: List[str] = names.DEFAULT_EVENTS_KERNEL, + **kwargs, + ) -> None: + """ + Constructor. + + :param session_name: the name of the tracing session + :param base_path: the base directory in which to create the trace directory + :param events_ust: the list of ROS UST events to enable + :param events_kernel: the list of kernel events to enable + """ + super().__init__(**kwargs) + self.__session_name = session_name + self.__path = os.path.join(base_path, session_name) + self.__events_ust = events_ust + self.__events_kernel = events_kernel + + 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() + + def _setup(self) -> None: + lttng.lttng_init( + self.__session_name, + self.__path, + ros_events=self.__events_ust, + kernel_events=self.__events_kernel) + + def _destroy(self, event: Event, context: LaunchContext) -> None: + lttng.lttng_fini(self.__session_name) + + def __repr__(self): + return ( + "Trace(" + f"session_name='{self.__session_name}', " + f"path='{self.__path}', " + f"num_events_ust={len(self.__events_ust)}, " + f"num_events_kernel={len(self.__events_kernel)})" + ) diff --git a/tracetools_test/package.xml b/tracetools_test/package.xml index 2a62e8d..abc5734 100644 --- a/tracetools_test/package.xml +++ b/tracetools_test/package.xml @@ -6,7 +6,7 @@ Separate test package for tracetools Christophe Bedard Ingo Luetkebohle - Apache Software License 2.0 + Apache 2.0 Christophe Bedard ament_cmake diff --git a/tracetools_trace/.gitignore b/tracetools_trace/.gitignore new file mode 100644 index 0000000..eef29c1 --- /dev/null +++ b/tracetools_trace/.gitignore @@ -0,0 +1,3 @@ +*~ +*.pyc + diff --git a/tracetools_trace/package.xml b/tracetools_trace/package.xml new file mode 100644 index 0000000..1361ead --- /dev/null +++ b/tracetools_trace/package.xml @@ -0,0 +1,24 @@ + + + + tracetools_trace + 0.0.1 + Tools for setting up tracing sessions + Christophe Bedard + Ingo Lütkebohle + Apache 2.0 + Christophe Bedard + + + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/tracetools_trace/setup.cfg b/tracetools_trace/setup.cfg new file mode 100644 index 0000000..6bbabc6 --- /dev/null +++ b/tracetools_trace/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script-dir=$base/lib/tracetools_trace +[install] +install-scripts=$base/lib/tracetools_trace diff --git a/tracetools_trace/setup.py b/tracetools_trace/setup.py new file mode 100644 index 0000000..c28f6b1 --- /dev/null +++ b/tracetools_trace/setup.py @@ -0,0 +1,28 @@ +from setuptools import find_packages +from setuptools import setup + +package_name = 'tracetools_trace' + +setup( + name=package_name, + version='0.0.1', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + maintainer='Christophe Bedard, Ingo Lütkebohle', + maintainer_email='fixed-term.christophe.bourquebedard@de.bosch.com, ingo.luetkebohle@de.bosch.com', + author='Christophe Bedard', + author_email='fixed-term.christophe.bourquebedard@de.bosch.com', + # url='', + keywords=['ROS'], + description='Tools for setting up tracing sessions', + entry_points={ + 'console_scripts': [ + f'trace = {package_name}.trace:main', + ], + }, + license='Apache 2.0', + tests_require=['pytest'], +) diff --git a/tracetools_trace/test/test_copyright.py b/tracetools_trace/test/test_copyright.py new file mode 100644 index 0000000..cf0fae3 --- /dev/null +++ b/tracetools_trace/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_trace/test/test_flake8.py b/tracetools_trace/test/test_flake8.py new file mode 100644 index 0000000..eff8299 --- /dev/null +++ b/tracetools_trace/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_trace/test/test_pep257.py b/tracetools_trace/test/test_pep257.py new file mode 100644 index 0000000..3aeb4d3 --- /dev/null +++ b/tracetools_trace/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 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_trace/tracetools_trace/__init__.py b/tracetools_trace/tracetools_trace/__init__.py new file mode 100644 index 0000000..4b18865 --- /dev/null +++ b/tracetools_trace/tracetools_trace/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/tracetools_trace/tracetools_trace/tools/__init__.py b/tracetools_trace/tracetools_trace/tools/__init__.py new file mode 100644 index 0000000..4b18865 --- /dev/null +++ b/tracetools_trace/tracetools_trace/tools/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/tracetools_trace/tracetools_trace/tools/args.py b/tracetools_trace/tracetools_trace/tools/args.py new file mode 100644 index 0000000..09938ae --- /dev/null +++ b/tracetools_trace/tracetools_trace/tools/args.py @@ -0,0 +1,67 @@ +# 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. + +"""Module containing parsing functions for tracing commands.""" + +import argparse +import time + +from . import names + + +class DefaultArgValueCompleter: + """Callable returning an arg's default value.""" + + def __init__(self, arg): + default = arg.default + self.list = default if isinstance(default, list) else [default] + + def __call__(self, **kwargs): + return self.list + + +def parse_args(): + """ + Parse args for tracing. + """ + parser = argparse.ArgumentParser(description='Setup and launch an LTTng tracing session.') + add_arguments(parser) + return parser.parse_args() + + +def add_arguments(parser): + parser.add_argument( + '--session-name', '-s', dest='session_name', + default=f'session-{time.strftime("%Y%m%d%H%M%S")}', + help='the name of the tracing session (default: session-YYYYMMDDHHMMSS)') + parser.add_argument( + '--path', '-p', dest='path', + default='/tmp', + help='path of the base directory for trace data (default: %(default)s)') + arg = parser.add_argument( + '--ust', '-u', nargs='*', dest='events_ust', default=names.DEFAULT_EVENTS_ROS, + help='the ROS UST events to enable (default: all events) ' + '[to disable all UST events, ' + 'provide this flag without any event name]') + arg.completer = DefaultArgValueCompleter(arg) + arg = parser.add_argument( + '--kernel', '-k', nargs='*', dest='events_kernel', + default=names.DEFAULT_EVENTS_KERNEL, + help='the kernel events to enable (default: all events) ' + '[to disable all UST events, ' + 'provide this flag without any event name]') + arg.completer = DefaultArgValueCompleter(arg) + parser.add_argument( + '--list', '-l', dest='list', action='store_true', + help='display lists of enabled events (default: %(default)s)') diff --git a/tracetools_trace/tracetools_trace/tools/lttng.py b/tracetools_trace/tracetools_trace/tools/lttng.py new file mode 100644 index 0000000..3c54c91 --- /dev/null +++ b/tracetools_trace/tracetools_trace/tools/lttng.py @@ -0,0 +1,309 @@ +# 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. + +"""Interface for tracing with LTTng.""" + +import sys + +# Temporary workaround +sys.path = ['/usr/local/lib/python3.6/site-packages'] + sys.path +from typing import List + +import lttng # noqa: E402 + +from .names import ( # noqa: E402 + DEFAULT_CONTEXT, + DEFAULT_EVENTS_KERNEL, + DEFAULT_EVENTS_ROS, +) + + +def lttng_init( + session_name: str, + full_path: str, + ros_events: List[str] = DEFAULT_EVENTS_ROS, + kernel_events: List[str] = DEFAULT_EVENTS_KERNEL, + context_names: List[str] = DEFAULT_CONTEXT +) -> None: + """ + Set up and start LTTng session. + + :param session_name: the name of the session + :param full_path: the full path to the main directory to write trace data to + :param ros_events: list of ROS events to enable + :param kernel_events: list of kernel events to enable + :param context_names: list of context elements to enable + """ + _lttng_setup(session_name, full_path, ros_events, kernel_events, context_names) + _lttng_start(session_name) + + +def lttng_fini(session_name: str) -> None: + """ + Stop and destroy LTTng session. + + :param session_name: the name of the session + """ + _lttng_stop(session_name) + _lttng_destroy(session_name) + + +def _lttng_setup( + session_name: str, + full_path: str, + ros_events: List[str] = DEFAULT_EVENTS_ROS, + kernel_events: List[str] = DEFAULT_EVENTS_KERNEL, + context_names: List[str] = DEFAULT_CONTEXT, + channel_name_ust: str = 'ros2', + channel_name_kernel: str = 'kchan', +) -> None: + """ + Set up LTTng session, with events and context. + + See: https://lttng.org/docs/#doc-core-concepts + + :param session_name: the name of the session + :param full_path: the full path to the main directory to write trace data to + :param ros_events: list of ROS events to enable + :param kernel_events: list of kernel events to enable + :param context_names: list of context elements to enable + :param channel_name_ust: the UST channel name + :param channel_name_kernel: the kernel channel name + """ + ust_enabled = ros_events is not None and len(ros_events) > 0 + kernel_enabled = kernel_events is not None and len(kernel_events) > 0 + + # Domains + if ust_enabled: + domain_ust = lttng.Domain() + domain_ust.type = lttng.DOMAIN_UST + # Per-user buffer + domain_ust.buf_type = lttng.BUFFER_PER_UID + channel_ust = lttng.Channel() + channel_ust.name = channel_name_ust + # Discard, do not overwrite + channel_ust.attr.overwrite = 0 + # 8 sub-buffers of 2 times the usual page size + channel_ust.attr.subbuf_size = 2 * 4096 + channel_ust.attr.num_subbuf = 8 + # Ignore switch timer interval and use read timer instead + channel_ust.attr.switch_timer_interval = 0 + channel_ust.attr.read_timer_interval = 200 + # mmap channel output instead of splice + channel_ust.attr.output = lttng.EVENT_MMAP + events_list_ust = _create_events(ros_events) + if kernel_enabled: + domain_kernel = lttng.Domain() + domain_kernel.type = lttng.DOMAIN_KERNEL + # Global buffer (only option for kernel domain) + domain_kernel.buf_type = lttng.BUFFER_GLOBAL + channel_kernel = lttng.Channel() + channel_kernel.name = channel_name_kernel + # Discard, do not overwrite + channel_kernel.attr.overwrite = 0 + # 8 sub-buffers of 8 times the usual page size, since + # there can be way more kernel events than UST events + channel_kernel.attr.subbuf_size = 8 * 4096 + channel_kernel.attr.num_subbuf = 8 + # Ignore switch timer interval and use read timer instead + channel_kernel.attr.switch_timer_interval = 0 + channel_kernel.attr.read_timer_interval = 200 + # mmap channel output instead of splice + channel_kernel.attr.output = lttng.EVENT_MMAP + events_list_kernel = _create_events(kernel_events) + + # Session + _create_session(session_name, full_path) + + # Handles, channels, events + handle_ust = None + if ust_enabled: + handle_ust = _create_handle(session_name, domain_ust) + _enable_channel(handle_ust, channel_ust) + _enable_events(handle_ust, events_list_ust, channel_ust.name) + handle_kernel = None + if kernel_enabled: + handle_kernel = _create_handle(session_name, domain_kernel) + _enable_channel(handle_kernel, channel_kernel) + _enable_events(handle_kernel, events_list_kernel, channel_kernel.name) + + # Context + context_list = _create_context_list(context_names) + enabled_handles = [h for h in [handle_ust, handle_kernel] if h is not None] + _add_context(enabled_handles, context_list) + + +def _lttng_start(session_name: str) -> None: + """ + Start LTTng session, and check for errors. + + :param session_name: the name of the session + """ + result = lttng.start(session_name) + if result < 0: + raise RuntimeError(f'failed to start tracing: {lttng.strerror(result)}') + + +def _lttng_stop(session_name: str) -> None: + """ + Stop LTTng session, and check for errors. + + :param session_name: the name of the session + """ + result = lttng.stop(session_name) + if result < 0: + raise RuntimeError(f'failed to stop tracing: {lttng.strerror(result)}') + + +def _lttng_destroy(session_name: str) -> None: + """ + Destroy LTTng session, and check for errors. + + :param session_name: the name of the session + """ + result = lttng.destroy(session_name) + if result < 0: + raise RuntimeError(f'failed to destroy tracing session: {lttng.strerror(result)}') + + +def _create_events(event_names_list: List[str]) -> List[lttng.Event]: + """ + Create events list from names. + + :param event_names_list: a list of names to create events for + :return: the list of events + """ + events_list = [] + for event_name in event_names_list: + e = lttng.Event() + e.name = event_name + e.type = lttng.EVENT_TRACEPOINT + e.loglevel_type = lttng.EVENT_LOGLEVEL_ALL + events_list.append(e) + return events_list + + +def _create_session(session_name: str, full_path: str) -> None: + """ + Create session from name and full directory path, and check for errors. + + :param session_name: the name of the session + :param full_path: the full path to the main directory to write trace data to + """ + result = lttng.create(session_name, full_path) + LTTNG_ERR_EXIST_SESS = 28 + if result == -LTTNG_ERR_EXIST_SESS: + # Sessions seem to persist, so if it already exists, + # just destroy it and try again + lttng_destroy(session_name) + result = lttng.create(session_name, full_path) + if result < 0: + raise RuntimeError(f'session creation failed: {lttng.strerror(result)}') + + +def _create_handle(session_name: str, domain: lttng.Domain) -> lttng.Handle: + """ + Create a handle for a given session name and a domain, and check for errors. + + :param session_name: the name of the session + :param domain: the domain to be used + :return: the handle + """ + handle = None + handle = lttng.Handle(session_name, domain) + if handle is None: + raise RuntimeError('handle creation failed') + return handle + + +def _enable_channel(handle: lttng.Handle, channel: lttng.Channel) -> None: + """ + Enable channel for a handle, and check for errors. + + :param handle: the handle to be used + :param channel: the channel to enable + """ + result = lttng.enable_channel(handle, channel) + if result < 0: + raise RuntimeError(f'channel enabling failed: {lttng.strerror(result)}') + + +def _enable_events( + handle: lttng.Handle, + events_list: List[lttng.Event], + channel_name: str, +) -> None: + """ + Enable events list for a given handle and channel name, and check for errors. + + :param handle: the handle to be used + :param events_list: the list of events to enable + :param channel_name: the name of the channel to associate + """ + for event in events_list: + result = lttng.enable_event(handle, event, channel_name) + if result < 0: + raise RuntimeError(f'event enabling failed: {lttng.strerror(result)}') + + +context_map = { + 'procname': lttng.EVENT_CONTEXT_PROCNAME, + 'pid': lttng.EVENT_CONTEXT_PID, + 'vpid': lttng.EVENT_CONTEXT_VPID, + 'vtid': lttng.EVENT_CONTEXT_VTID, +} + + +def _context_name_to_type(context_name: str) -> int: + """ + Convert from context name to LTTng enum/constant type. + + :param context_name: the generic name for the context + :return: the associated type + """ + return context_map.get(context_name) + + +def _create_context_list(context_names_list: List[str]) -> List[lttng.EventContext]: + """ + Create context list from names, and check for errors. + + :param context_names_list: the list of context names + :return: the event context list + """ + context_list = [] + for c in context_names_list: + ec = lttng.EventContext() + context_type = _context_name_to_type(c) + if context_type is not None: + ec.ctx = context_type + context_list.append(ec) + return context_list + + +def _add_context( + handles: List[lttng.Handle], + context_list: List[lttng.EventContext], +) -> None: + """ + Add context list to given handles, and check for errors. + + :param handles: the list of handles for which to add context + :param context_list: the list of event contexts to add to the handles + """ + for handle in handles: + for contex in context_list: + result = lttng.add_context(handle, contex, None, None) + if result < 0: + raise RuntimeError(f'failed to add context: {lttng.strerror(result)}') diff --git a/tracetools_trace/tracetools_trace/tools/names.py b/tracetools_trace/tracetools_trace/tools/names.py new file mode 100644 index 0000000..43ad7b0 --- /dev/null +++ b/tracetools_trace/tracetools_trace/tools/names.py @@ -0,0 +1,81 @@ +# 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. + +"""Lists of names (events, context) for tracing.""" + +EVENTS_KERNEL = [ + 'block_rq_complete', + 'block_rq_insert', + 'block_rq_issue', + 'block_bio_frontmerge', + 'irq_softirq_entry', + 'irq_softirq_raise', + 'irq_softirq_exit', + 'irq_handler_entry', + 'irq_handler_exit', + 'lttng_statedump_process_state', + 'lttng_statedump_start', + 'lttng_statedump_end', + 'lttng_statedump_network_interface', + 'lttng_statedump_block_device', + 'net_dev_queue', + 'netif_receive_skb', + 'net_if_receive_skb', + 'power_cpu_frequency', + 'sched_switch', + 'sched_waking', + 'sched_pi_setprio', + 'sched_process_fork', + 'sched_process_exit', + 'sched_process_free', + 'sched_wakeup', + 'sched_migrate', + 'sched_migrate_task', + 'timer_hrtimer_start', + 'timer_hrtimer_cancel', + 'timer_hrtimer_expire_entry', + 'timer_hrtimer_expire_exit', +] + +DEFAULT_EVENTS_KERNEL = [ + 'power_cpu_frequency', + 'sched_switch', + 'sched_waking', + 'sched_wakeup', +] + +DEFAULT_EVENTS_ROS = [ + 'ros2:rcl_init', + 'ros2:rcl_node_init', + 'ros2:rcl_publisher_init', + 'ros2:rcl_subscription_init', + 'ros2:rclcpp_subscription_callback_added', + 'ros2:rcl_service_init', + 'ros2:rclcpp_service_callback_added', + 'ros2:rcl_client_init', + 'ros2:rcl_timer_init', + 'ros2:rclcpp_timer_callback_added', + 'ros2:rclcpp_callback_register', + 'ros2:callback_start', + 'ros2:callback_end', +] + +DEFAULT_CONTEXT = [ + 'procname', + 'perf:thread:instructions', + 'perf:thread:cycles', + 'perf:thread:cpu-cycles', + 'vpid', + 'vtid', +] diff --git a/tracetools_trace/tracetools_trace/trace.py b/tracetools_trace/tracetools_trace/trace.py new file mode 100644 index 0000000..055e63d --- /dev/null +++ b/tracetools_trace/tracetools_trace/trace.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# 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. + +"""Entrypoint/script to setup and start an LTTng tracing session.""" + +import os + +from tracetools_trace.tools import args +from tracetools_trace.tools import lttng +from tracetools_trace.tools import names + + +def main(): + params = args.parse_args() + + session_name = params.session_name + base_path = params.path + full_path = os.path.join(base_path, session_name) + ros_events = params.events_ust + kernel_events = params.events_kernel + + ust_enabled = len(ros_events) > 0 + kernel_enabled = len(kernel_events) > 0 + if ust_enabled: + print(f'UST tracing enabled ({len(ros_events)} events)') + if params.list: + print(f'\tevents: {ros_events}') + else: + print('UST tracing disabled') + if kernel_enabled: + print(f'kernel tracing enabled ({len(kernel_events)} events)') + if params.list: + print(f'\tevents: {kernel_events}') + else: + print('kernel tracing disabled') + + print(f'writting tracing session to: {full_path}') + input('press enter to start...') + lttng.lttng_init(session_name, full_path, ros_events=ros_events, kernel_events=kernel_events) + input('press enter to stop...') + + print('stopping & destroying tracing session') + lttng.lttng_fini(session_name)