diff --git a/test_ros2trace_analysis/.coveragerc b/test_ros2trace_analysis/.coveragerc
new file mode 100644
index 0000000..2536a4b
--- /dev/null
+++ b/test_ros2trace_analysis/.coveragerc
@@ -0,0 +1,4 @@
+[run]
+omit =
+ setup.py
+ test/*
diff --git a/test_ros2trace_analysis/.gitignore b/test_ros2trace_analysis/.gitignore
new file mode 100644
index 0000000..eef29c1
--- /dev/null
+++ b/test_ros2trace_analysis/.gitignore
@@ -0,0 +1,3 @@
+*~
+*.pyc
+
diff --git a/test_ros2trace_analysis/CHANGELOG.rst b/test_ros2trace_analysis/CHANGELOG.rst
new file mode 100644
index 0000000..e69de29
diff --git a/test_ros2trace_analysis/package.xml b/test_ros2trace_analysis/package.xml
new file mode 100644
index 0000000..f18feea
--- /dev/null
+++ b/test_ros2trace_analysis/package.xml
@@ -0,0 +1,32 @@
+
+
+
+ test_ros2trace_analysis
+ 8.3.0
+ Tests for the ros2trace_analysis package.
+ Christophe Bedard
+ Apache 2.0
+ https://index.ros.org/p/test_ros2trace_analysis/
+ https://github.com/ros-tracing/tracetools_analysis
+ https://github.com/ros-tracing/tracetools_analysis/issues
+ Christophe Bedard
+
+ ament_copyright
+ ament_flake8
+ ament_mypy
+ ament_pep257
+ ament_xmllint
+ launch
+ launch_ros
+ python3-pytest
+ ros2run
+ ros2trace
+ ros2trace_analysis
+ test_tracetools
+ tracetools
+ tracetools_trace
+
+
+ ament_python
+
+
diff --git a/test_ros2trace_analysis/resource/test_ros2trace_analysis b/test_ros2trace_analysis/resource/test_ros2trace_analysis
new file mode 100644
index 0000000..e69de29
diff --git a/test_ros2trace_analysis/setup.py b/test_ros2trace_analysis/setup.py
new file mode 100644
index 0000000..582f6df
--- /dev/null
+++ b/test_ros2trace_analysis/setup.py
@@ -0,0 +1,26 @@
+from setuptools import find_packages
+from setuptools import setup
+
+package_name = 'test_ros2trace_analysis'
+
+setup(
+ name=package_name,
+ version='3.0.0',
+ packages=find_packages(exclude=['test']),
+ data_files=[
+ ('share/' + package_name, ['package.xml']),
+ ('share/ament_index/resource_index/packages',
+ ['resource/' + package_name]),
+ ],
+ install_requires=['setuptools'],
+ zip_safe=True,
+ maintainer='Christophe Bedard',
+ maintainer_email='bedard.christophe@gmail.com',
+ author='Christophe Bedard',
+ author_email='bedard.christophe@gmail.com',
+ url='https://github.com/ros-tracing/tracetools_analysis',
+ keywords=[],
+ description='Tests for the ros2trace_analysis package.',
+ license='Apache 2.0',
+ tests_require=['pytest'],
+)
diff --git a/test_ros2trace_analysis/test/test_copyright.py b/test_ros2trace_analysis/test/test_copyright.py
new file mode 100644
index 0000000..cf0fae3
--- /dev/null
+++ b/test_ros2trace_analysis/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/test_ros2trace_analysis/test/test_flake8.py b/test_ros2trace_analysis/test/test_flake8.py
new file mode 100644
index 0000000..27ee107
--- /dev/null
+++ b/test_ros2trace_analysis/test/test_flake8.py
@@ -0,0 +1,25 @@
+# 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_with_errors
+import pytest
+
+
+@pytest.mark.flake8
+@pytest.mark.linter
+def test_flake8():
+ rc, errors = main_with_errors(argv=[])
+ assert rc == 0, \
+ 'Found %d code style errors / warnings:\n' % len(errors) + \
+ '\n'.join(errors)
diff --git a/test_ros2trace_analysis/test/test_mypy.py b/test_ros2trace_analysis/test/test_mypy.py
new file mode 100644
index 0000000..331a3b8
--- /dev/null
+++ b/test_ros2trace_analysis/test/test_mypy.py
@@ -0,0 +1,22 @@
+# Copyright 2019 Canonical Ltd
+#
+# 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_mypy.main import main
+import pytest
+
+
+@pytest.mark.mypy
+@pytest.mark.linter
+def test_mypy():
+ assert main(argv=[]) == 0, 'Found errors'
diff --git a/test_ros2trace_analysis/test/test_pep257.py b/test_ros2trace_analysis/test/test_pep257.py
new file mode 100644
index 0000000..0e38a6c
--- /dev/null
+++ b/test_ros2trace_analysis/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/test_ros2trace_analysis/test/test_ros2trace_analysis/test_process.py b/test_ros2trace_analysis/test/test_ros2trace_analysis/test_process.py
new file mode 100644
index 0000000..d2f7eb1
--- /dev/null
+++ b/test_ros2trace_analysis/test/test_ros2trace_analysis/test_process.py
@@ -0,0 +1,175 @@
+# Copyright 2024 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.
+
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import tempfile
+from typing import Dict
+from typing import List
+from typing import Optional
+import unittest
+
+from launch import LaunchDescription
+from launch import LaunchService
+from launch_ros.actions import Node
+from tracetools_trace.tools.lttng import is_lttng_installed
+
+
+def are_tracepoints_included() -> bool:
+ """
+ Check if tracing instrumentation is enabled and if tracepoints are included.
+
+ :return: True if tracepoints are included, False otherwise
+ """
+ if not is_lttng_installed():
+ return False
+ process = subprocess.run(
+ ['ros2', 'run', 'tracetools', 'status'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding='utf-8',
+ )
+ return 0 == process.returncode
+
+
+@unittest.skipIf(not is_lttng_installed(minimum_version='2.9.0'), 'LTTng is required')
+class TestROS2TraceAnalysisCLI(unittest.TestCase):
+
+ def __init__(self, *args) -> None:
+ super().__init__(
+ *args,
+ )
+
+ def create_test_tmpdir(self, test_name: str) -> str:
+ prefix = self.__class__.__name__ + '__' + test_name
+ return tempfile.mkdtemp(prefix=prefix)
+
+ def run_command(
+ self,
+ args: List[str],
+ *,
+ env: Optional[Dict[str, str]] = None,
+ ) -> subprocess.Popen:
+ print('=>running:', args)
+ process_env = os.environ.copy()
+ process_env['PYTHONUNBUFFERED'] = '1'
+ if env:
+ process_env.update(env)
+ return subprocess.Popen(
+ args,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding='utf-8',
+ env=process_env,
+ )
+
+ def wait_and_print_command_output(
+ self,
+ process: subprocess.Popen,
+ ) -> int:
+ stdout, stderr = process.communicate()
+ stdout = stdout.strip(' \r\n\t')
+ stderr = stderr.strip(' \r\n\t')
+ print('=>stdout:\n' + stdout)
+ print('=>stderr:\n' + stderr)
+ return process.wait()
+
+ def run_command_and_wait(
+ self,
+ args: List[str],
+ *,
+ env: Optional[Dict[str, str]] = None,
+ ) -> int:
+ process = self.run_command(args, env=env)
+ return self.wait_and_print_command_output(process)
+
+ def run_nodes(self) -> None:
+ nodes = [
+ Node(
+ package='test_tracetools',
+ executable='test_ping',
+ output='screen',
+ ),
+ Node(
+ package='test_tracetools',
+ executable='test_pong',
+ output='screen',
+ ),
+ ]
+ ld = LaunchDescription(nodes)
+ ls = LaunchService()
+ ls.include_launch_description(ld)
+ exit_code = ls.run()
+ self.assertEqual(0, exit_code)
+
+ def test_process_bad_input_path(self) -> None:
+ tmpdir = self.create_test_tmpdir('test_process_bad_input_path')
+
+ # No input path
+ ret = self.run_command_and_wait(['ros2', 'trace-analysis', 'process'])
+ self.assertEqual(2, ret)
+
+ # Does not exist
+ ret = self.run_command_and_wait(['ros2', 'trace-analysis', 'process', ''])
+ self.assertEqual(1, ret)
+ fake_input = os.path.join(tmpdir, 'doesnt_exist')
+ ret = self.run_command_and_wait(['ros2', 'trace-analysis', 'process', fake_input])
+ self.assertEqual(1, ret)
+
+ # Exists but empty
+ empty_input = os.path.join(tmpdir, 'empty')
+ os.mkdir(empty_input)
+ ret = self.run_command_and_wait(['ros2', 'trace-analysis', 'process', empty_input])
+ self.assertEqual(1, ret)
+
+ # Exists but converted file empty
+ empty_converted_file = os.path.join(empty_input, 'converted')
+ Path(empty_converted_file).touch()
+ ret = self.run_command_and_wait(['ros2', 'trace-analysis', 'process', empty_input])
+ self.assertEqual(1, ret)
+
+ shutil.rmtree(tmpdir)
+
+ @unittest.skipIf(not are_tracepoints_included(), 'tracepoints are required')
+ def test_process(self) -> None:
+ tmpdir = self.create_test_tmpdir('test_process')
+ session_name = 'test_process'
+
+ # Run and trace nodes
+ ret = self.run_command_and_wait(
+ [
+ 'ros2', 'trace',
+ 'start', session_name,
+ '--path', tmpdir,
+ ],
+ )
+ self.assertEqual(0, ret)
+ trace_dir = os.path.join(tmpdir, session_name)
+ self.run_nodes()
+ ret = self.run_command_and_wait(['ros2', 'trace', 'stop', session_name])
+ self.assertEqual(0, ret)
+
+ # Process trace
+ ret = self.run_command_and_wait(['ros2', 'trace-analysis', 'process', trace_dir])
+ self.assertEqual(0, ret)
+
+ # Check that converted file exists and isn't empty
+ converted_file = os.path.join(trace_dir, 'converted')
+ self.assertTrue(os.path.isfile(converted_file))
+ self.assertGreater(os.path.getsize(converted_file), 0)
+
+ shutil.rmtree(tmpdir)
diff --git a/test_ros2trace_analysis/test/test_xmllint.py b/test_ros2trace_analysis/test/test_xmllint.py
new file mode 100644
index 0000000..f46285e
--- /dev/null
+++ b/test_ros2trace_analysis/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/test_ros2trace_analysis/test_ros2trace_analysis/__init__.py b/test_ros2trace_analysis/test_ros2trace_analysis/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tracetools_analysis/package.xml b/tracetools_analysis/package.xml
index 00fc67b..5b45a93 100644
--- a/tracetools_analysis/package.xml
+++ b/tracetools_analysis/package.xml
@@ -14,6 +14,7 @@
Christophe Bedard
tracetools_read
+ tracetools_trace
python3-pandas
jupyter-notebook
diff --git a/tracetools_analysis/tracetools_analysis/processor/ros2.py b/tracetools_analysis/tracetools_analysis/processor/ros2.py
index 8e69965..9c45e4e 100644
--- a/tracetools_analysis/tracetools_analysis/processor/ros2.py
+++ b/tracetools_analysis/tracetools_analysis/processor/ros2.py
@@ -20,6 +20,7 @@ from typing import Set
from typing import Tuple
from tracetools_read import get_field
+from tracetools_trace.tools import tracepoints as tp
from . import EventHandler
from . import EventMetadata
@@ -41,55 +42,55 @@ class Ros2Handler(EventHandler):
"""Create a Ros2Handler."""
# Link a ROS trace event to its corresponding handling method
handler_map: HandlerMap = {
- 'ros2:rcl_init':
+ tp.rcl_init:
self._handle_rcl_init,
- 'ros2:rcl_node_init':
+ tp.rcl_node_init:
self._handle_rcl_node_init,
- 'ros2:rmw_publisher_init':
+ tp.rmw_publisher_init:
self._handle_rmw_publisher_init,
- 'ros2:rcl_publisher_init':
+ tp.rcl_publisher_init:
self._handle_rcl_publisher_init,
- 'ros2:rclcpp_publish':
+ tp.rclcpp_publish:
self._handle_rclcpp_publish,
- 'ros2:rcl_publish':
+ tp.rcl_publish:
self._handle_rcl_publish,
- 'ros2:rmw_publish':
+ tp.rmw_publish:
self._handle_rmw_publish,
- 'ros2:rmw_subscription_init':
+ tp.rmw_subscription_init:
self._handle_rmw_subscription_init,
- 'ros2:rcl_subscription_init':
+ tp.rcl_subscription_init:
self._handle_rcl_subscription_init,
- 'ros2:rclcpp_subscription_init':
+ tp.rclcpp_subscription_init:
self._handle_rclcpp_subscription_init,
- 'ros2:rclcpp_subscription_callback_added':
+ tp.rclcpp_subscription_callback_added:
self._handle_rclcpp_subscription_callback_added,
- 'ros2:rmw_take':
+ tp.rmw_take:
self._handle_rmw_take,
- 'ros2:rcl_take':
+ tp.rcl_take:
self._handle_rcl_take,
- 'ros2:rclcpp_take':
+ tp.rclcpp_take:
self._handle_rclcpp_take,
- 'ros2:rcl_service_init':
+ tp.rcl_service_init:
self._handle_rcl_service_init,
- 'ros2:rclcpp_service_callback_added':
+ tp.rclcpp_service_callback_added:
self._handle_rclcpp_service_callback_added,
- 'ros2:rcl_client_init':
+ tp.rcl_client_init:
self._handle_rcl_client_init,
- 'ros2:rcl_timer_init':
+ tp.rcl_timer_init:
self._handle_rcl_timer_init,
- 'ros2:rclcpp_timer_callback_added':
+ tp.rclcpp_timer_callback_added:
self._handle_rclcpp_timer_callback_added,
- 'ros2:rclcpp_timer_link_node':
+ tp.rclcpp_timer_link_node:
self._handle_rclcpp_timer_link_node,
- 'ros2:rclcpp_callback_register':
+ tp.rclcpp_callback_register:
self._handle_rclcpp_callback_register,
- 'ros2:callback_start':
+ tp.callback_start:
self._handle_callback_start,
- 'ros2:callback_end':
+ tp.callback_end:
self._handle_callback_end,
- 'ros2:rcl_lifecycle_state_machine_init':
+ tp.rcl_lifecycle_state_machine_init:
self._handle_rcl_lifecycle_state_machine_init,
- 'ros2:rcl_lifecycle_transition':
+ tp.rcl_lifecycle_transition:
self._handle_rcl_lifecycle_transition,
}
super().__init__(
@@ -104,7 +105,7 @@ class Ros2Handler(EventHandler):
@staticmethod
def required_events() -> Set[str]:
return {
- 'ros2:rcl_init',
+ tp.rcl_init,
}
@property