Skip to content

Commit 26dccbf

Browse files
InvincibleRMCfujitatomoyachristophebedard
authored
[ros2doctor] Environment Report (#1045)
* Add environment information Signed-off-by: Michael Carlstrom <[email protected]> * Add support for different casings Signed-off-by: Michael Carlstrom <[email protected]> * Update to use ros2doctor list rather than dynamic lookup Signed-off-by: Michael Carlstrom <[email protected]> * fix bug in name Signed-off-by: Michael Carlstrom <[email protected]> * Update ros2doctor/ros2doctor/api/__init__.py Co-authored-by: Tomoya Fujita <[email protected]> Signed-off-by: Michael Carlstrom <[email protected]> * removing pure typing changes Signed-off-by: Michael Carlstrom <[email protected]> * remove extra newline Signed-off-by: Michael Carlstrom <[email protected]> * add back collections from typing import Signed-off-by: Michael Carlstrom <[email protected]> * Add tracing environment variables and switch order Signed-off-by: Michael Carlstrom <[email protected]> * add test Signed-off-by: Michael Carlstrom <[email protected]> * Fix spelling Signed-off-by: Michael Carlstrom <[email protected]> * Add warning Signed-off-by: Michael Carlstrom <[email protected]> * remove junk Signed-off-by: Michael Carlstrom <[email protected]> * remove log Signed-off-by: Michael Carlstrom <[email protected]> * test setUp Signed-off-by: Michael Carlstrom <[email protected]> * test imp Signed-off-by: Michael Carlstrom <[email protected]> * test with rmw isolation Signed-off-by: Michael Carlstrom <[email protected]> * test other arg Signed-off-by: Michael Carlstrom <[email protected]> * sort environment first Signed-off-by: Michael Carlstrom <[email protected]> * finally something that works Signed-off-by: Michael Carlstrom <[email protected]> * switch zenoh environ var Signed-off-by: Michael Carlstrom <[email protected]> * cleanup Signed-off-by: Michael Carlstrom <[email protected]> * cleanup Signed-off-by: Michael Carlstrom <[email protected]> * change rust log files Signed-off-by: Michael Carlstrom <[email protected]> * rename test file Signed-off-by: Michael Carlstrom <[email protected]> * update test name Signed-off-by: Michael Carlstrom <[email protected]> * simplify launch Signed-off-by: Michael Carlstrom <[email protected]> * remove bonus space in name Signed-off-by: Michael Carlstrom <[email protected]> * Update ros2doctor/ros2doctor/api/environment.py Co-authored-by: Christophe Bedard <[email protected]> Signed-off-by: Michael Carlstrom <[email protected]> * update with feedback Signed-off-by: Michael Carlstrom <[email protected]> * Update ros2doctor/test/test_environment_report.py Co-authored-by: Christophe Bedard <[email protected]> Signed-off-by: Michael Carlstrom <[email protected]> --------- Signed-off-by: Michael Carlstrom <[email protected]> Co-authored-by: Tomoya Fujita <[email protected]> Co-authored-by: Christophe Bedard <[email protected]>
1 parent 3ac25ce commit 26dccbf

File tree

4 files changed

+312
-2
lines changed

4 files changed

+312
-2
lines changed

ros2doctor/ros2doctor/api/__init__.py

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from typing import Final
1516
from typing import List
1617
from typing import Set
1718
from typing import Tuple
@@ -26,15 +27,104 @@
2627
from ros2doctor.api.format import doctor_warn
2728

2829

30+
# List of Environment Variables compiled from github.com/ros2/ros2cli/issues/1046
31+
# TODO(@fujitatomoya): In the future maybe get from centralized storage via rcl/rmw interfaces.
32+
# NOTE: In alphabetical order for ease of searching.
33+
ROS_ENVIRONMENT_VARIABLES: Final = [
34+
'RCL_LOGGING_SPDLOG_EXPERIMENTAL_OLD_FLUSHING_BEHAVIOR',
35+
'ROS_AUTOMATIC_DISCOVERY_RANGE',
36+
'ROS_DISABLE_LOANED_MESSAGES',
37+
'ROS_DOMAIN_ID',
38+
'ROS_DISTRO',
39+
'ROS_HOME',
40+
'ROS_LOG_DIR',
41+
'ROS_SECURITY_ENABLE',
42+
'ROS_SECURITY_ENCLAVE_OVERRIDE',
43+
'ROS_SECURITY_KEYSTORE',
44+
'ROS_SECURITY_STRATEGY',
45+
'ROS_STATIC_PEERS',
46+
'ROS_TRACE_DIR'
47+
'RMW_IMPLEMENTATION',
48+
'TRACETOOLS_RUNTIME_DISABLE'
49+
]
50+
51+
52+
RCUTILS_ENVIRONMENT_VARIABLES: Final = [
53+
'RCUTILS_COLORIZED_OUTPUT',
54+
'RCUTILS_CONSOLE_OUTPUT_FORMAT',
55+
'RCUTILS_CONSOLE_STDOUT_LINE_BUFFERED',
56+
'RCUTILS_LOGGING_BUFFERED_STREAM',
57+
'RCUTILS_LOGGING_USE_STDOUT'
58+
]
59+
60+
61+
RMW_FASTRTPS_ENVIRONMENT_VARIABLES: Final = [
62+
'FASTDDS_BUILTIN_TRANSPORTS',
63+
'FASTRTPS_DEFAULT_PROFILES_FILE',
64+
'RMW_FASTRTPS_PUBLICATION_MODE',
65+
'RMW_FASTRTPS_USE_QOS_FROM_XML'
66+
]
67+
68+
69+
RMW_ZENOH_CPP_ENVIRONMENT_VARIABLES: Final = [
70+
'RUST_LOG',
71+
'ZENOH_CONFIG_OVERRIDE',
72+
'ZENOH_ROUTER_CHECK_ATTEMPTS',
73+
'ZENOH_ROUTER_CONFIG_URI',
74+
'ZENOH_SESSION_CONFIG_URI'
75+
]
76+
77+
78+
RMW_CONNEXTDDS_ENVIRONMENT_VARIABLES: Final = [
79+
'RMW_CONNEXT_CYCLONE_COMPATIBILITY_MODE',
80+
'RMW_CONNEXT_DISABLE_RELIABILITY_OPTIMIZATIONS',
81+
'RMW_CONNEXT_DISABLE_FAST_ENDPOINT_DISCOVERY',
82+
'RMW_CONNEXT_DISABLE_LARGE_DATA_OPTIMIZATIONS',
83+
'RMW_CONNEXT_ENDPOINT_QOS_OVERRIDE_POLICY',
84+
'RMW_CONNEXT_ENV_UDP_INTERFACE',
85+
'RMW_CONNEXT_INITIAL_PEERS',
86+
'RMW_CONNEXT_OLD_RMW_COMPATIBILITY_MODE',
87+
'RMW_CONNEXT_PARTICIPANT_QOS_OVERRIDE_POLICY',
88+
'RMW_CONNEXT_REQUEST_REPLY_MAPPING',
89+
'RMW_CONNEXT_SECURITY_LOG_FILE',
90+
'RMW_CONNEXT_SECURITY_LOG_PUBLISH',
91+
'RMW_CONNEXT_SECURITY_LOG_VERBOSITY',
92+
'RMW_CONNEXT_USE_DEFAULT_PUBLISH_MODE',
93+
]
94+
95+
96+
RMW_CYCLONEDDS_ENVIRONMENT_VARIABLES: Final = [
97+
'CYCLONEDDS_URI'
98+
]
99+
100+
101+
ALL_ENVIRONMENT_VARIABLES: Final = [
102+
*ROS_ENVIRONMENT_VARIABLES,
103+
*RCUTILS_ENVIRONMENT_VARIABLES,
104+
*RMW_FASTRTPS_ENVIRONMENT_VARIABLES,
105+
*RMW_ZENOH_CPP_ENVIRONMENT_VARIABLES,
106+
*RMW_CONNEXTDDS_ENVIRONMENT_VARIABLES,
107+
*RMW_CYCLONEDDS_ENVIRONMENT_VARIABLES
108+
]
109+
110+
RMW_ENVIRONMENT_VARIABLES: Final = {
111+
'rmw_connextdds': RMW_CONNEXTDDS_ENVIRONMENT_VARIABLES,
112+
'rmw_cyclonedds_cpp': RMW_CYCLONEDDS_ENVIRONMENT_VARIABLES,
113+
'rmw_fastrtps_cpp': RMW_FASTRTPS_ENVIRONMENT_VARIABLES,
114+
'rmw_fastrtps_dynamic_cpp': RMW_FASTRTPS_ENVIRONMENT_VARIABLES,
115+
'rmw_zenoh_cpp': RMW_ZENOH_CPP_ENVIRONMENT_VARIABLES,
116+
}
117+
118+
29119
class DoctorCheck:
30120
"""Abstract base class of ros2doctor check."""
31121

32122
def category(self) -> str:
33123
""":return: string linking checks and reports."""
34124
raise NotImplementedError
35125

36-
def check(self) -> bool:
37-
""":return: boolean indicating result of checks."""
126+
def check(self) -> 'Result':
127+
""":return: Result indicating result of checks."""
38128
raise NotImplementedError
39129

40130

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Copyright 2025 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
from typing import Literal
17+
18+
from rclpy.utilities import get_rmw_implementation_identifier
19+
from ros2doctor.api import DoctorReport
20+
from ros2doctor.api import RCUTILS_ENVIRONMENT_VARIABLES
21+
from ros2doctor.api import Report
22+
from ros2doctor.api import RMW_ENVIRONMENT_VARIABLES
23+
from ros2doctor.api import ROS_ENVIRONMENT_VARIABLES
24+
25+
26+
class EnvironmentReport(DoctorReport):
27+
"""Report current ROS and RMW environment variable information."""
28+
29+
def category(self) -> Literal['environment']:
30+
return 'environment'
31+
32+
def report(self) -> Report:
33+
environment_report = Report('ROS ENVIRONMENT')
34+
35+
rmw_name = get_rmw_implementation_identifier()
36+
37+
rmw_environment_variables: list[str] = RMW_ENVIRONMENT_VARIABLES.get(rmw_name, [])
38+
39+
ros_variable_list: list[str] = []
40+
rmw_variable_list: list[str] = []
41+
rcutils_variable_list: list[str] = []
42+
43+
for key, value in sorted(os.environ.items()):
44+
if key in ROS_ENVIRONMENT_VARIABLES:
45+
ros_variable_list.append(f'{key}={value}')
46+
if key in RCUTILS_ENVIRONMENT_VARIABLES:
47+
rcutils_variable_list.append(f'{key}={value}')
48+
if key in rmw_environment_variables:
49+
rmw_variable_list.append(f'{key}={value}')
50+
51+
environment_report.add_to_report('ROS environment variables',
52+
', '.join(ros_variable_list))
53+
environment_report.add_to_report('rcutils environment variables',
54+
', '.join(rcutils_variable_list))
55+
56+
if rmw_environment_variables:
57+
environment_report.add_to_report(
58+
'rmw environment variables',
59+
', '.join(rmw_variable_list))
60+
return environment_report

ros2doctor/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
'ActionReport = ros2doctor.api.action:ActionReport',
5959
'QoSCompatibilityReport = ros2doctor.api.qos_compatibility:QoSCompatibilityReport',
6060
'PackageReport = ros2doctor.api.package:PackageReport',
61+
'EnvironmentReport = ros2doctor.api.environment:EnvironmentReport'
6162
],
6263
'ros2cli.extension_point': [
6364
'ros2doctor.verb = ros2doctor.verb:VerbExtension',
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Copyright 2025 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import contextlib
16+
import os
17+
import sys
18+
from typing import Any
19+
from typing import Generator
20+
import unittest
21+
22+
from launch import LaunchDescription
23+
from launch import LaunchService
24+
from launch.actions import ExecuteProcess
25+
from launch.actions import SetEnvironmentVariable
26+
import launch_testing
27+
import launch_testing.actions
28+
import launch_testing.asserts
29+
import launch_testing.markers
30+
import launch_testing.tools
31+
from launch_testing_ros.actions import EnableRmwIsolation
32+
33+
import pytest
34+
35+
from rclpy.utilities import get_available_rmw_implementations
36+
from ros2cli.helpers import get_rmw_additional_env
37+
38+
39+
# Skip cli tests on Windows while they exhibit pathological behavior
40+
# https://github.com/ros2/build_farmer/issues/248
41+
if sys.platform.startswith('win'):
42+
pytest.skip(
43+
'CLI tests can block for a pathological amount of time on Windows.',
44+
allow_module_level=True)
45+
46+
47+
CYCLONEDDS_XML = '<CycloneDDS><Domain><General></General></Domain></CycloneDDS>'
48+
49+
50+
@pytest.mark.rostest
51+
@launch_testing.parametrize('rmw_implementation', get_available_rmw_implementations())
52+
@launch_testing.markers.keep_alive
53+
def generate_test_description(rmw_implementation: str) -> tuple[LaunchDescription,
54+
dict[str, Any]]:
55+
additional_env = get_rmw_additional_env(rmw_implementation)
56+
additional_env['PYTHONUNBUFFERED'] = '1'
57+
set_env_actions = [SetEnvironmentVariable(k, v) for k, v in additional_env.items()]
58+
59+
return LaunchDescription([
60+
ExecuteProcess(
61+
cmd=['ros2', 'daemon', 'stop'],
62+
name='daemon-stop',
63+
on_exit=[
64+
*set_env_actions,
65+
EnableRmwIsolation(),
66+
ExecuteProcess(
67+
cmd=['ros2', 'daemon', 'start'],
68+
name='daemon-start',
69+
additional_env=additional_env,
70+
on_exit=[
71+
SetEnvironmentVariable('ROS_AUTOMATIC_DISCOVERY_RANGE', 'SUBNET'),
72+
SetEnvironmentVariable('ROS_DISTRO', 'rolling'),
73+
SetEnvironmentVariable('ROS_DISABLE_LOANED_MESSAGES', '0'),
74+
SetEnvironmentVariable('RCUTILS_COLORIZED_OUTPUT', '0'),
75+
SetEnvironmentVariable('FASTDDS_BUILTIN_TRANSPORTS', 'UDPv6'),
76+
SetEnvironmentVariable('ZENOH_ROUTER_CHECK_ATTEMPTS', '10'),
77+
SetEnvironmentVariable('RMW_CONNEXT_INITIAL_PEERS', 'CONNEXTBOO'),
78+
SetEnvironmentVariable('CYCLONEDDS_URI', CYCLONEDDS_XML),
79+
launch_testing.actions.ReadyToTest()
80+
]
81+
)
82+
]
83+
)
84+
]), locals()
85+
86+
87+
class TestEnvironmentReport(unittest.TestCase):
88+
89+
@classmethod
90+
def setUpClass(
91+
cls,
92+
launch_service: LaunchService,
93+
proc_info: launch_testing.tools.process.ActiveProcInfoHandler,
94+
proc_output: launch_testing.tools.process.ActiveIoHandler,
95+
rmw_implementation: str,
96+
) -> None:
97+
cls.rmw_implementation = rmw_implementation
98+
99+
@contextlib.contextmanager
100+
def launch_doctor_command(
101+
self,
102+
arguments
103+
) -> Generator[launch_testing.tools.process.ProcessProxy, None, None]:
104+
additional_env = get_rmw_additional_env(rmw_implementation)
105+
additional_env['PYTHONUNBUFFERED'] = '1'
106+
doctor_command_action = ExecuteProcess(
107+
cmd=['ros2', 'doctor', *arguments],
108+
additional_env=additional_env,
109+
name='ros2doctor-environment-report',
110+
output='screen'
111+
)
112+
with launch_testing.tools.launch_process(
113+
launch_service, doctor_command_action, proc_info, proc_output
114+
) as doctor_command:
115+
yield doctor_command
116+
117+
cls.launch_doctor_command = launch_doctor_command
118+
119+
if rmw_implementation == 'rmw_cyclonedds_cpp':
120+
cls.expected_line = f'CYCLONEDDS_URI={CYCLONEDDS_XML}'
121+
elif rmw_implementation == 'rmw_connextdds':
122+
cls.expected_line = 'RMW_CONNEXT_INITIAL_PEERS=CONNEXTBOO'
123+
elif rmw_implementation == 'rmw_zenoh_cpp':
124+
config = os.environ['ZENOH_CONFIG_OVERRIDE']
125+
cls.expected_line = f'RUST_LOG=z=error, ZENOH_CONFIG_OVERRIDE={config}, '
126+
'ZENOH_ROUTER_CHECK_ATTEMPTS=10'
127+
elif rmw_implementation == 'rmw_fastrtps_cpp':
128+
cls.expected_line = 'FASTDDS_BUILTIN_TRANSPORTS=UDPv6'
129+
else:
130+
cls.expected_line = ''
131+
132+
@launch_testing.markers.retry_on_failure(times=5, delay=1)
133+
def test_environment_report(self) -> None:
134+
135+
for argument in ['-r', '--report']:
136+
with self.launch_doctor_command(
137+
arguments=[argument]
138+
) as doctor_command:
139+
assert doctor_command.wait_for_shutdown(timeout=10)
140+
assert doctor_command.exit_code == launch_testing.asserts.EXIT_OK
141+
142+
# Due to isolation ROS_DOMAIN_ID is unique.
143+
# ROS_DOMAIN_ID Does not seem to be set for zenoh with EnableRmwIsolation.
144+
domain_id_line = ''
145+
if self.rmw_implementation == 'rmw_zenoh_cpp':
146+
domain_id_line = ''
147+
else:
148+
domain_id = os.environ['ROS_DOMAIN_ID']
149+
domain_id_line = f', ROS_DOMAIN_ID={domain_id}'
150+
151+
assert launch_testing.tools.expect_output(
152+
expected_lines=[
153+
'ROS environment variables : ROS_AUTOMATIC_DISCOVERY_RANGE=SUBNET, '
154+
f'ROS_DISABLE_LOANED_MESSAGES=0, ROS_DISTRO=rolling{domain_id_line}',
155+
'rcutils environment variables : RCUTILS_COLORIZED_OUTPUT=0',
156+
f'rmw environment variables : {self.expected_line}'
157+
],
158+
text=doctor_command.output
159+
)

0 commit comments

Comments
 (0)