How to define inter-nodes data path#
Latency definition section is empty just after an architecture object is loaded from a set of CTF-based recorded data. If you want to observe data flow on targeted paths, you should add the target paths to the architecture object. As a path is combination of multiple nodes and topics, it may be laborious to list their names. To mitigate such burden, CARET serves a function to search the targeted path. The targeted path is added to the architecture object via Python API.
Basic usage to find and add target path#
Listing nodes and topics by their names is laborious. CARET serve a helpful method, arch.search_paths
, to search candidates that you want to observe.
The following sample code shows usage of arch.search_paths
method.
# Architecture object is loaded to variable of arch
paths = arch.search_paths('source_node',
'destination_node')
type(paths) # list of multiple paths
paths[0].summary.pprint() # shows nodes and topics in paths[0]
In the sample, paths
is a list including all possible paths between source_node
and destination_node
, whose type is PathStructValue
. If you are satisfied with output from paths[0].summary.pprint()
, you have to add paths[0]
to the arch
object as below.
arch.add_path('target_path', paths[0])
arch.export('new_architecture_with_path.yaml')
In the above sample, paths[0]
is named as target_path
and registered to the arch
object. arch
object is exported to a new architecture file for reuse.
If you want to restore the paths[0]
object with new_architecture_with_path.yaml
file, arch.get_path()
method will help you to do so.
arch = Architecture('yaml', 'new_architecture_with_path.yaml')
path = arch.get_path('target_path') # path object is same as paths[0] in the previous sample
Efficient target path search#
As explained above, Architecture.search_paths()
returns list of multiple paths.
The list size will be too large to find target path if application has large numbers of nodes and distance between source and destination node is long. In worse case, Architecture.search_paths()
keep searching and does not return paths
variable after hours passes.
Architecture.search_paths()
method serves four options to narrow down possible paths as below.
- Additional nodes as variable length arguments
- Limiting maximum number of nodes between given nodes with
max_node_depth
- Node filter which excludes paths including specific nodes
- Communication filter which excludes paths including specific topics
In short, Architecture.search_paths
is defined as follows.
search_paths(
*node_names: 'str',
max_node_depth: 'Optional[int]' = None,
node_filter: 'Optional[Callable[[str], bool]]' = None,
communication_filter: 'Optional[Callable[[str], bool]]' = None
) -> 'List[PathStructValue]'
The following sub-sections will explain their roles and usages in details.
Additional nodes#
In the previous example, Architecture.search_paths()
had two arguments source_node
and destination_node
. However, the number of nodes given to Architecture.search_paths()
is variable and not always two. You can add other nodes to Architecture.search_paths()
as below, and you will get a list including multiple paths which passes all given nodes.
# Architecture object is loaded to variable of arch
paths = arch.search_paths('source_node',
'intermediate_node_1',
'intermediate_node_2',
'destination_node')
paths
is a list including multiple paths which pass source_node
, intermediate_node_1
, intermediate_node_2
, and destination_node
. They are allowed to pass another node, but all chosen nodes are passed in order.
Limiting maximum number of nodes#
Architecture.search_paths()
will scan all paths as possible if you don't give max_node_depth
argument. max_node_depth
means maximum number between given nodes. The number of candidate paths will be suppressed by this argument.
This argument will be helpful When you waste much time for Architecture.search_paths()
.
The usage is shown as below. This can be used with another approach to filter candidates.
# Architecture object is loaded to variable of arch
paths = arch.search_paths('source_node',
'intermediate_node_1',
'destination_node',
max_node_depth=10)
max_node_depth
does not always limit the maximum number of nodes between source and destination. If you give 3 nodes to arch.search_paths
as shown above, max_node_depth
does not limits the maximum number of nodes between source_node
and destination_node
. In this example, max_node_depth
limits the number of nodes between source_node
and intermediate_node_1
, and that between intermediate_node_1
and destination_node
.
Node and topic filter#
As node filter is similar to communication filter, they are explained together in this section.
With node filter and communication filter, Architecture.search_paths()
excludes paths which includes selected nodes or topics. They support regular expression.
The following sample code shows usage.
import re
# name list of nodes to be excluded
node_filters = [
re.compile(r'/_ros2cli_/*'),
re.compile(r'/launch_ros_*'),
]
# name list of topics to be excluded
comm_filters = [
re.compile(r'/tf/*'),
]
def comm_filter(topic_name: str) -> bool:
can_pass = True
for comm_filter in comm_filters:
can_pass &= not bool(comm_filter.search(topic_name))
return can_pass
def node_filter(node_name: str) -> bool:
can_pass = True
for node_filter in node_filters:
can_pass &= not bool(node_filter.search(node_name))
return can_pass
paths = arch.search_paths(
'/start_node',
'/intermediate_node'
'/end_node',
max_node_depth=30,
node_filter = node_filter,
communication_filter = comm_filter)
Path combining#
Architecture.combine_path()
combines two paths which are found by Architecture.search_paths()
.
By searching short paths and combining them repeatedly, you can get a target path. It is sometimes more efficient than searching a longer path directly according to "divide-and-conquer" method.
Usage of Architecture.combine_path()
is as following.
paths_1 = arch.search_paths('source_node',
'intermediate_node')
paths_2 = arch.search_paths('intermediate_node',
'destination_node')
target_path = arch.combine_path(paths_1[0], paths_2[0])
arch.add_path('combined_path_name', target_path)
arch.export('new_architecture.yaml')
An exception is raised for pairs that cannot be combined.
Considering first/last callback#
In CARET, the path is defined as [node_name]-[topic_name]-... -[topic_name]-[node_name]
(for more information in Path).
In the default configuration, the path analysis does not include the callback processing time either at the first node or at the last node of the above path. The following two processing times are not included by default in path analysis.
- Processing time in the first node; from
callback_start
torclcpp_publish
orrclcpp_intra_publish
- Processing time in the last node; from
callback_start
tocallback_end
These processing times are evaluated using the following options.
Path.include_first_callback
- Default:
False
- If
True
, the processing time in the first node is included in the path analysis. The callback_start in the first node is that of a trace data whose timestamp is the closest to publish and thread id (tid) is the same as publish. Note that a callback object of the callback_start in the first node can vary if the node has several callbacks and any of them runs before publish.
- Default:
Path.include_last_callback
- Default:
False
- If
True
, the processing time in the last node is included in the path analysis.
- Default:
Usage of Path.include_first_callback/include_last_callback
is as following.
app = Application(arch, lttng)
path_name = 'target_path'
target_path = app.get_path(path_name)
target_path.include_first_callback = True # Include first callback in path analysis.
target_path.include_last_callback = True # Include last callback in path analysis.