Skip to content

Commit 5ab9796

Browse files
Barry-Xu-2018morlov-apexaiMichaelOrlov
authored
Implement action recording and display info about recorded action (#1939)
* Implement action recording and display info about recorded action Signed-off-by: Barry Xu <[email protected]> * Address review comment of first round Signed-off-by: Barry Xu <[email protected]> * Address review comments from fujitatomoya Signed-off-by: Barry Xu <[email protected]> * genstub Signed-off-by: Barry Xu <[email protected]> * Optimize some parts of the code Signed-off-by: Barry Xu <[email protected]> * Fix an issue with the display when there is no service data Signed-off-by: Barry Xu <[email protected]> * Address review comments Signed-off-by: Barry Xu <[email protected]> * Change a variable name to make the code clearer and more readable Signed-off-by: Barry Xu <[email protected]> * Address review comments Signed-off-by: Barry Xu <[email protected]> * Simplify if-else block with a shorter name service_info Signed-off-by: Michael Orlov <[email protected]> * Throw exception by default in the get_action_type_for_info(topic_type) Signed-off-by: Michael Orlov <[email protected]> * Remove type conversion in sequential_writer - Rationale: We shall store the original message definition for recorded topics by design. This is also true for service events. Signed-off-by: Michael Orlov <[email protected]> * Calculate actions status and feedback statistics in read_service_and_action_info Signed-off-by: Michael Orlov <[email protected]> * Address some other small style changes and nitpicks Signed-off-by: Michael Orlov <[email protected]> * Add default section with exception to sorting functions Signed-off-by: Michael Orlov <[email protected]> * Use structural bindings in the test_action_utils.cpp Signed-off-by: Michael Orlov <[email protected]> * Add Doxygen comments to the action_utils.hpp Signed-off-by: Michael Orlov <[email protected]> * Avoid using ContainsRegex on Windows to handle the '|' character Signed-off-by: Barry Xu <[email protected]> --------- Signed-off-by: Barry Xu <[email protected]> Signed-off-by: Michael Orlov <[email protected]> Co-authored-by: Michael Orlov <[email protected]> Co-authored-by: Michael Orlov <[email protected]>
1 parent c8291b1 commit 5ab9796

File tree

55 files changed

+3131
-372
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+3131
-372
lines changed

ros2bag/ros2bag/verb/info.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ def add_arguments(self, parser, cli_name): # noqa: D102
2828
)
2929
parser.add_argument(
3030
'-v', '--verbose', action='store_true',
31-
help='Display request/response information for services'
31+
help='Display verbose information about request/response services, actions and'
32+
' print messages size contribution on per topic bases.'
3233
)
3334
available_sorting_methods = Info().get_sorting_methods()
3435
parser.add_argument(

ros2bag/ros2bag/verb/record.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,27 +70,34 @@ def add_recorder_arguments(parser: ArgumentParser) -> None:
7070
parser.add_argument(
7171
'--services', type=str, metavar='ServiceName', nargs='+',
7272
help='Space-delimited list of services to record.')
73+
parser.add_argument(
74+
'--actions', type=str, metavar='ActionName', nargs='+',
75+
help='Space-delimited list of actions to record.')
7376
parser.add_argument(
7477
'--topic-types', nargs='+', default=[], metavar='TopicType',
7578
help='Space-delimited list of topic types to record.')
7679
parser.add_argument(
7780
'-a', '--all', action='store_true',
78-
help='Record all topics and services (Exclude hidden topic).')
81+
help='Record all topics, services and actions (Exclude hidden topic).')
7982
parser.add_argument(
8083
'--all-topics', action='store_true',
8184
help='Record all topics (Exclude hidden topic).')
8285
parser.add_argument(
8386
'--all-services', action='store_true',
8487
help='Record all services via service event topics.')
88+
parser.add_argument(
89+
'--all-actions', action='store_true',
90+
help='Record all actions via internal topics and service event topics.')
8591
parser.add_argument(
8692
'-e', '--regex', default='',
8793
help='Record only topics and services containing provided regular expression. '
88-
'Note: --all, --all-topics or --all-services will override --regex.')
94+
'Note: --all, --all-topics, --all-services or --all-actions will override --regex.')
8995
parser.add_argument(
9096
'--exclude-regex', default='',
9197
help='Exclude topics and services containing provided regular expression. '
9298
'Works on top of '
93-
'--all, --all-topics, --all-services, --topics, --services or --regex.')
99+
'--all, --all-topics, --all-services, --all-actions, --topics, --services, --actions '
100+
'or --regex.')
94101
parser.add_argument(
95102
'--exclude-topic-types', type=str, default=[], metavar='ExcludeTopicTypes', nargs='+',
96103
help='Space-delimited list of topic types not being recorded. '
@@ -103,6 +110,10 @@ def add_recorder_arguments(parser: ArgumentParser) -> None:
103110
'--exclude-services', type=str, metavar='ServiceName', nargs='+',
104111
help='Space-delimited list of services not being recorded. '
105112
'Works on top of --all, --all-services, --services or --regex.')
113+
parser.add_argument(
114+
'--exclude-actions', type=str, metavar='ActionName', nargs='+',
115+
help='Space-delimited list of actions not being recorded. '
116+
'Works on top of --all, --all-actions, --actions or --regex.')
106117

107118
# Discovery behavior
108119
parser.add_argument(
@@ -217,10 +228,11 @@ def add_recorder_arguments(parser: ArgumentParser) -> None:
217228

218229

219230
def check_necessary_argument(args):
220-
# At least one options out of --all, --all-topics, --all-services, --services, --topics,
221-
# --topic-types or --regex must be used
222-
if not (args.all or args.all_topics or args.all_services or
231+
# At least one options out of --all, --all-topics, --all-services, --all-actions, --services,
232+
# --actions --topics, --topic-types or --regex must be used
233+
if not (args.all or args.all_topics or args.all_services or args.all_actions or
223234
(args.services and len(args.services) > 0) or
235+
(args.actions and len(args.actions) > 0) or
224236
(args.topics and len(args.topics) > 0) or
225237
(args.topic_types and len(args.topic_types) > 0) or args.regex):
226238
return False
@@ -239,9 +251,9 @@ def validate_parsed_arguments(args, uri) -> str:
239251

240252
if args.exclude_regex and not \
241253
(args.all or args.all_topics or args.topic_types or args.all_services or
242-
args.regex):
254+
args.all_actions or args.regex):
243255
return print_error('--exclude-regex argument requires either --all, '
244-
'--all-topics, --topic-types, --all-services or --regex')
256+
'--all-topics, --topic-types, --all-services, --all-actions or --regex')
245257

246258
if args.exclude_topics and not \
247259
(args.all or args.all_topics or args.topic_types or args.regex):
@@ -257,14 +269,22 @@ def validate_parsed_arguments(args, uri) -> str:
257269
return print_error('--exclude-services argument requires either --all, --all-services '
258270
'or --regex')
259271

272+
if args.exclude_actions and not (args.all or args.all_actions or args.regex):
273+
return print_error('--exclude-actions argument requires either --all, --all-actions '
274+
'or --regex')
275+
260276
if (args.all or args.all_services) and args.services:
261277
print(print_warn('--all or --all-services will override --services'))
262278

263279
if (args.all or args.all_topics) and args.topics:
264280
print(print_warn('--all or --all-topics will override --topics'))
265281

266-
if (args.all or args.all_topics or args.all_services) and args.regex:
267-
print(print_warn('--all, --all-topics or --all-services will override --regex'))
282+
if (args.all or args.all_actions) and args.actions:
283+
print(print_warn('--all or --all-actions will override --actions'))
284+
285+
if (args.all or args.all_topics or args.all_services or args.all_actions) and args.regex:
286+
print(print_warn('--all, --all-topics --all-services or --all-actions will override '
287+
'--regex'))
268288

269289
if os.path.isdir(uri):
270290
return print_error("Output folder '{}' already exists.".format(uri))
@@ -281,6 +301,8 @@ def validate_parsed_arguments(args, uri) -> str:
281301
if args.compression_queue_size < 0:
282302
return print_error('Compression queue size must be at least 0.')
283303

304+
return None
305+
284306

285307
class RecordVerb(VerbExtension):
286308
"""Record ROS data to a bag."""
@@ -330,11 +352,14 @@ def main(self, *, args): # noqa: D102
330352
record_options = RecordOptions()
331353
record_options.all_topics = args.all_topics or args.all
332354
record_options.all_services = args.all_services or args.all
355+
record_options.all_actions = args.all_actions or args.all
333356
record_options.is_discovery_disabled = args.no_discovery
334357
record_options.topics = args.topics
335358
record_options.topic_types = args.topic_types
336359
# Convert service name to service event topic name
337360
record_options.services = convert_service_to_service_event_topic(args.services)
361+
record_options.actions = args.actions if args.actions else []
362+
338363
record_options.exclude_topic_types = args.exclude_topic_types
339364
record_options.rmw_serialization_format = args.serialization_format
340365
record_options.topic_polling_interval = datetime.timedelta(
@@ -344,6 +369,7 @@ def main(self, *, args): # noqa: D102
344369
record_options.exclude_topics = args.exclude_topics if args.exclude_topics else []
345370
record_options.exclude_service_events = \
346371
convert_service_to_service_event_topic(args.exclude_services)
372+
record_options.exclude_actions = args.exclude_actions if args.exclude_actions else []
347373
record_options.node_prefix = NODE_NAME_PREFIX
348374
record_options.compression_mode = args.compression_mode
349375
record_options.compression_format = args.compression_format

ros2bag/test/test_recorder_args_parser.py

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ def test_recorder_services_list_argument(test_arguments_parser):
6363
assert output_path.as_posix() == args.output
6464

6565

66+
def test_recorder_actions_list_argument(test_arguments_parser):
67+
"""Test recorder --actions list argument parser."""
68+
output_path = RESOURCES_PATH / 'ros2bag_tmp_file'
69+
args = test_arguments_parser.parse_args(
70+
['--actions', 'action1', 'action2', '--output', output_path.as_posix()]
71+
)
72+
assert ['action1', 'action2'] == args.actions
73+
assert output_path.as_posix() == args.output
74+
75+
6676
def test_recorder_services_and_positional_topics_list_arguments(test_arguments_parser):
6777
"""Test recorder --services list and positional topics list arguments parser."""
6878
output_path = RESOURCES_PATH / 'ros2bag_tmp_file'
@@ -74,6 +84,17 @@ def test_recorder_services_and_positional_topics_list_arguments(test_arguments_p
7484
assert output_path.as_posix() == args.output
7585

7686

87+
def test_recorder_actions_and_positional_topics_list_arguments(test_arguments_parser):
88+
"""Test recorder --actions list and positional topics list arguments parser."""
89+
output_path = RESOURCES_PATH / 'ros2bag_tmp_file'
90+
args = test_arguments_parser.parse_args(
91+
['--output', output_path.as_posix(),
92+
'--actions', 'action1', 'action2', '--', 'topic1', 'topic2'])
93+
assert ['action1', 'action2'] == args.actions
94+
assert ['topic1', 'topic2'] == args.topics_positional
95+
assert output_path.as_posix() == args.output
96+
97+
7798
def test_recorder_services_and_optional_topics_list_arguments(test_arguments_parser):
7899
"""Test recorder --services list and optional --topics list arguments parser."""
79100
output_path = RESOURCES_PATH / 'ros2bag_tmp_file'
@@ -85,6 +106,41 @@ def test_recorder_services_and_optional_topics_list_arguments(test_arguments_par
85106
assert output_path.as_posix() == args.output
86107

87108

109+
def test_recorder_actions_and_optional_topics_list_arguments(test_arguments_parser):
110+
"""Test recorder --actions list and optional --topics list arguments parser."""
111+
output_path = RESOURCES_PATH / 'ros2bag_tmp_file'
112+
args = test_arguments_parser.parse_args(
113+
['--output', output_path.as_posix(),
114+
'--actions', 'action1', 'action2', '--topics', 'topic1', 'topic2'])
115+
assert ['action1', 'action2'] == args.actions
116+
assert ['topic1', 'topic2'] == args.topics
117+
assert output_path.as_posix() == args.output
118+
119+
120+
def test_recorder_services_and_actions_list_arguments(test_arguments_parser):
121+
"""Test recorder --services list and --actions list arguments parser."""
122+
output_path = RESOURCES_PATH / 'ros2bag_tmp_file'
123+
args = test_arguments_parser.parse_args(
124+
['--output', output_path.as_posix(),
125+
'--services', 'service1', 'service2', '--actions', 'action1', 'action2'])
126+
assert ['service1', 'service2'] == args.services
127+
assert ['action1', 'action2'] == args.actions
128+
assert output_path.as_posix() == args.output
129+
130+
131+
def test_recorder_services_actions_and_topics_list_arguments(test_arguments_parser):
132+
"""Test recorder --services list, --actions list and --topics list arguments parser."""
133+
output_path = RESOURCES_PATH / 'ros2bag_tmp_file'
134+
args = test_arguments_parser.parse_args(
135+
['--output', output_path.as_posix(),
136+
'--services', 'service1', 'service2', '--actions', 'action1', 'action2',
137+
'--topics', 'topic1', 'topic2'])
138+
assert ['service1', 'service2'] == args.services
139+
assert ['action1', 'action2'] == args.actions
140+
assert ['topic1', 'topic2'] == args.topics
141+
assert output_path.as_posix() == args.output
142+
143+
88144
def test_recorder_topic_types_list_argument(test_arguments_parser):
89145
"""Test recorder --topic-types list argument parser."""
90146
output_path = RESOURCES_PATH / 'ros2bag_tmp_file'
@@ -124,6 +180,16 @@ def test_recorder_exclude_services_list_argument(test_arguments_parser):
124180
assert output_path.as_posix() == args.output
125181

126182

183+
def test_recorder_exclude_actions_list_argument(test_arguments_parser):
184+
"""Test recorder --exclude-actions list argument parser."""
185+
output_path = RESOURCES_PATH / 'ros2bag_tmp_file'
186+
args = test_arguments_parser.parse_args(
187+
['--exclude-actions', 'action1', 'action2', '--output', output_path.as_posix()]
188+
)
189+
assert ['action1', 'action2'] == args.exclude_actions
190+
assert output_path.as_posix() == args.output
191+
192+
127193
def test_recorder_custom_data_list_argument(test_arguments_parser):
128194
"""Test recorder --custom-data list argument parser."""
129195
output_path = RESOURCES_PATH / 'ros2bag_tmp_file'
@@ -138,21 +204,22 @@ def test_recorder_validate_exclude_regex_needs_inclusive_args(test_arguments_par
138204
"""Test that --exclude-regex needs inclusive arguments."""
139205
output_path = RESOURCES_PATH / 'ros2bag_tmp_file'
140206
args = test_arguments_parser.parse_args(
141-
['--exclude-regex', '%s-%s', '--services', 'service1', '--topics', 'topic1',
142-
'--output', output_path.as_posix()]
207+
['--exclude-regex', '%s-%s', '--services', 'service1', '--topics', 'topic1', '--actions',
208+
'action1', '--output', output_path.as_posix()]
143209
)
144210
assert '%s-%s' == args.exclude_regex
145211
assert args.all is False
146212
assert args.all_topics is False
147213
assert [] == args.topic_types
148214
assert args.all_services is False
215+
assert args.all_actions is False
149216
assert '' == args.regex
150217

151218
uri = args.output or datetime.datetime.now().strftime('rosbag2_%Y_%m_%d-%H_%M_%S')
152219
error_str = validate_parsed_arguments(args, uri)
153220
assert error_str is not None
154221
expected_output = '--exclude-regex argument requires either --all, ' \
155-
'--all-topics, --topic-types, --all-services or --regex'
222+
'--all-topics, --topic-types, --all-services, --all-actions or --regex'
156223
matches = expected_output in error_str
157224
assert matches, ERROR_STRING_MSG.format(expected_output, error_str)
158225

@@ -227,3 +294,28 @@ def test_recorder_validate_exclude_services_needs_inclusive_args(test_arguments_
227294
'or --regex'
228295
matches = expected_output in error_str
229296
assert matches, ERROR_STRING_MSG.format(expected_output, error_str)
297+
298+
299+
def test_recorder_validate_exclude_actions_needs_inclusive_args(test_arguments_parser):
300+
"""Test that --exclude-actions needs at least --all, --all-actions or --regex arguments."""
301+
output_path = RESOURCES_PATH / 'ros2bag_tmp_file'
302+
args = test_arguments_parser.parse_args(
303+
['--exclude-actions', 'action1', '--actions', 'action1', '--all-topics',
304+
'--output', output_path.as_posix()]
305+
)
306+
assert ['action1'] == args.exclude_actions
307+
assert args.all is False
308+
assert args.all_topics is True
309+
assert [] == args.topic_types
310+
assert args.all_services is False
311+
assert args.all_actions is False
312+
assert ['action1'] == args.actions
313+
assert '' == args.regex
314+
assert '' == args.exclude_regex
315+
316+
uri = args.output or datetime.datetime.now().strftime('rosbag2_%Y_%m_%d-%H_%M_%S')
317+
error_str = validate_parsed_arguments(args, uri)
318+
assert error_str is not None
319+
expected_output = '--exclude-actions argument requires either --all, --all-actions or --regex'
320+
matches = expected_output in error_str
321+
assert matches, ERROR_STRING_MSG.format(expected_output, error_str)

rosbag2_cpp/CMakeLists.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ add_library(${PROJECT_NAME} SHARED
7373
src/rosbag2_cpp/writer.cpp
7474
src/rosbag2_cpp/writers/sequential_writer.cpp
7575
src/rosbag2_cpp/reindexer.cpp
76-
src/rosbag2_cpp/service_utils.cpp)
76+
src/rosbag2_cpp/service_utils.cpp
77+
src/rosbag2_cpp/action_utils.cpp)
7778

7879
target_link_libraries(${PROJECT_NAME} PUBLIC
7980
pluginlib::pluginlib
@@ -312,6 +313,16 @@ if(BUILD_TESTING)
312313
${test_msgs_TARGETS}
313314
)
314315
endif()
316+
317+
ament_add_gmock(test_action_utils
318+
test/rosbag2_cpp/test_action_utils.cpp)
319+
if(TARGET test_action_utils)
320+
target_link_libraries(test_action_utils
321+
${PROJECT_NAME}
322+
rosbag2_test_common::rosbag2_test_common
323+
${test_msgs_TARGETS}
324+
)
325+
endif()
315326
endif()
316327

317328
ament_package()

0 commit comments

Comments
 (0)