Document tr_types.py

This commit is contained in:
Maximilian Schmeller 2022-12-28 13:39:57 +09:00
parent db97f2dece
commit 7f3b6c5aa1

View file

@ -13,7 +13,18 @@ Timestamp = namedtuple("Timestamp", ["timestamp"])
class Index(Generic[IdxItemType]): class Index(Generic[IdxItemType]):
"""
This class implements a sized, iterable collection that allows building indices on its content.
These indices are accessed by `index.by_<index_field>(<lookup_value>)`.
1-1, 1-n and n-m indices are supported. The indices are hash-, not range-based.
`IdxItemType` is expected to have a `timestamp` field by which this collection sorts its items.
"""
def __init__(self, items: Iterable[IdxItemType], **idx_fields): def __init__(self, items: Iterable[IdxItemType], **idx_fields):
"""
Construct an index.
:param items: An iterable of items to add to the index. Items have to have a `timestamp` field
:param idx_fields: Keyword args of the shape `<field_name> = <False|True|'n-to-m'>`. For each `<field_name>`, an index (1 to 1 for `False`, 1 to n for `True`, n to m for `'n-to-m'` is generated which can be accessed by `index.by_<field_name>(<lookup_value)` later.
"""
self.__idx_fields = idx_fields.copy() self.__idx_fields = idx_fields.copy()
self.__items = None self.__items = None
self.__indices = None self.__indices = None
@ -21,46 +32,70 @@ class Index(Generic[IdxItemType]):
self.rebuild(items) self.rebuild(items)
def rebuild(self, items: Optional[Iterable[IdxItemType]] = None): def rebuild(self, items: Optional[Iterable[IdxItemType]] = None):
"""
Clears all items and index structure contents, inserts the given `items` and recomputes all indices defined in the constructor.
:param items: The items to overwrite the index's contents. If `None`, the current items in the index remain
"""
def sort_key(item): def sort_key(item):
return item.timestamp return item.timestamp
# Keep self.__items if `None` is given as an argument, otherwise overwrite them.
if items is not None: if items is not None:
self.__items = list(items) self.__items = list(items)
# Sort items and clear built indices
self.__items.sort(key=sort_key) self.__items.sort(key=sort_key)
self.__indices = {} self.__indices = {}
# Build each index one-by-one that was requested in the constructor
for idx_name, is_multi in self.__idx_fields.items(): for idx_name, is_multi in self.__idx_fields.items():
index = {} index = {}
self.__indices[idx_name] = index self.__indices[idx_name] = index
if is_multi in (True, 'n-to-m'): # Accept either True (1-n) or 'n-to-m' (n-m) as value
if is_multi in (True, 'n-to-m'): # Multi-index case (True = 1-n, 'n-to-m' = n-m)
for item in self.__items: for item in self.__items:
if is_multi == 'n-to-m': if is_multi == 'n-to-m':
keys = getattr(item, idx_name) keys = getattr(item, idx_name) # Returns a list of multiple keys (n-m)
else: else:
keys = [getattr(item, idx_name)] # Only one key (1-n keys = [getattr(item, idx_name)] # Only one key (1-n)
# Insert the item into the index for each of its keys
# Multi-indices return a list for every key
for key in keys: for key in keys:
if key not in index: if key not in index:
index[key] = [] index[key] = []
index[key].append(item) # Also sorted since items are processed in order and only filtered here # Also sorted since items are processed in order and only filtered here
index[key].append(item)
elif not is_multi: # Unique index elif not is_multi: # Unique index
duplicate_indices = defaultdict(lambda: 0) duplicate_indices = defaultdict(lambda: 0)
# Insert item into the index at its key.
# Duplicates are accepted with a warning, although they should not exist.
# ROS 2 tracing ID duplication etc. causes them to occur though.
# This has not been observed to cause problems within the Autoware stack,
# only some info on ROS 2-internal stuff is lost.
for item in self.__items: for item in self.__items:
key = getattr(item, idx_name) key = getattr(item, idx_name)
if key in index: if key in index:
duplicate_indices[key] += 1 duplicate_indices[key] += 1
# Unique indices return a single item for every key
index[key] = item index[key] = item
if duplicate_indices: if duplicate_indices:
print(f"[DEBUG] Duplicate Indices in {idx_name}") print(f"[DEBUG] Duplicate Indices in {idx_name}")
else: else: # No valid index type given
raise ValueError(f"is_multi has to equal one of the following: (False, True, 'n-to-m') but is {is_multi}") raise ValueError(f"is_multi has to equal one of the following: (False, True, 'n-to-m') but is {is_multi}")
def append(self, items: Iterable[IdxItemType]): def append(self, items: Iterable[IdxItemType]):
"""
Append `items` to an existing index and insert them into the index structures defined in the constructor.
:param items: The items to add to the collection
"""
self.rebuild(list(self.__items) + list(items)) self.rebuild(list(self.__items) + list(items))
def clear(self): def clear(self):
"""
Clear all items and index structure contents. Keep index definitions for the case that items are added after this call.
"""
self.rebuild([]) self.rebuild([])
def __iter__(self): def __iter__(self):
@ -70,6 +105,12 @@ class Index(Generic[IdxItemType]):
return len(self.__items) return len(self.__items)
def __getattr__(self, item: str): def __getattr__(self, item: str):
"""
Returns the index pointed at by `item`.
The index is a function accepting one key, and returning either one item or a list of items depending on whether it is a unique or multi-index.
:param item: The index name, preceded by `'by_'`.
:return: The requested index function
"""
if not item.startswith("by_"): if not item.startswith("by_"):
return AttributeError( return AttributeError(
f"Not found in index: '{item}'. Index lookups must be of the shape 'by_<index_field>'.") f"Not found in index: '{item}'. Index lookups must be of the shape 'by_<index_field>'.")
@ -85,6 +126,11 @@ class Index(Generic[IdxItemType]):
@dataclass @dataclass
class TrContext: class TrContext:
"""
Contains all data that is needed to represent a tracing session.
The contained data is postprocessed, interlinked and indexed after being retrieved from a ros2_tracing `Ros2Handler`.
"""
nodes: Index['TrNode'] nodes: Index['TrNode']
publishers: Index['TrPublisher'] publishers: Index['TrPublisher']
subscriptions: Index['TrSubscription'] subscriptions: Index['TrSubscription']
@ -99,6 +145,10 @@ class TrContext:
_uuid: UUID _uuid: UUID
def __init__(self, handler: Ros2Handler): def __init__(self, handler: Ros2Handler):
"""
Build the context from a `Ros2Handler` instance.
:param handler: The `Ros2Handler` instance to build the context from
"""
print("[TrContext] Processing ROS 2 objects from traces...") print("[TrContext] Processing ROS 2 objects from traces...")
self.nodes = Index(df_to_type_list(handler.data.nodes, TrNode, _c=self), self.nodes = Index(df_to_type_list(handler.data.nodes, TrNode, _c=self),
@ -149,6 +199,9 @@ class TrContext:
@dataclass @dataclass
class TrNode: class TrNode:
"""
The representation of a ROS 2 node in the tracing context.
"""
id: int id: int
timestamp: int timestamp: int
tid: int tid: int
@ -184,6 +237,9 @@ class TrNode:
@dataclass @dataclass
class TrPublisher: class TrPublisher:
"""
The representation of a ROS 2 publisher in the tracing context.
"""
id: int id: int
timestamp: int timestamp: int
node_handle: int node_handle: int
@ -217,6 +273,9 @@ class TrPublisher:
@dataclass @dataclass
class TrSubscription: class TrSubscription:
"""
The representation of a ROS 2 subscription in the tracing context.
"""
id: int id: int
timestamp: int timestamp: int
node_handle: int node_handle: int
@ -250,6 +309,9 @@ class TrSubscription:
@dataclass @dataclass
class TrTimer: class TrTimer:
"""
The representation of a ROS 2 timer in the tracing context.
"""
id: int id: int
timestamp: int timestamp: int
period: int period: int
@ -276,6 +338,9 @@ class TrTimer:
@dataclass @dataclass
class TrTimerNodeLink: class TrTimerNodeLink:
"""
The relation connecting timers to nodes in ROS 2 tracing data.
"""
id: int id: int
timestamp: int timestamp: int
node_handle: int node_handle: int
@ -289,6 +354,9 @@ class TrTimerNodeLink:
@dataclass @dataclass
class TrSubscriptionObject: class TrSubscriptionObject:
"""
The relation connecting subscriptions to callback objects to nodes in ROS 2 tracing data.
"""
id: int id: int
timestamp: int timestamp: int
subscription_handle: int subscription_handle: int
@ -311,6 +379,9 @@ class TrSubscriptionObject:
@dataclass @dataclass
class TrCallbackObject: class TrCallbackObject:
"""
The relation connecting callback instances to subscriptions/timers/etc. in ROS 2 tracing data.
"""
id: int # (reference) = subscription_object.id | timer.id | .... id: int # (reference) = subscription_object.id | timer.id | ....
timestamp: int timestamp: int
callback_object: int callback_object: int
@ -343,6 +414,9 @@ class TrCallbackObject:
@dataclass @dataclass
class TrPublishInstance: class TrPublishInstance:
"""
A publication of a message in ROS 2 tracing data.
"""
publisher_handle: int publisher_handle: int
timestamp: float timestamp: float
message: int message: int
@ -361,6 +435,9 @@ class TrPublishInstance:
@dataclass @dataclass
class TrCallbackInstance: class TrCallbackInstance:
"""
An invocation of a callback (from a subscription or timer) in ROS 2 tracing data.
"""
callback_object: int callback_object: int
timestamp: float timestamp: float
duration: float duration: float
@ -388,6 +465,11 @@ class TrCallbackInstance:
@dataclass @dataclass
class TrCallbackSymbol: class TrCallbackSymbol:
"""
The C++ symbol corresponding to a callback in ROS 2 tracing data.
This is typically a very convoluted name with lots of C++ wrappers and almost no symbols or identifiers preserved.
Use `repr(matching.subscriptions.sanitize(tr_callback_symbol.symbol))` to get a readable name.
"""
id: int # callback_object id: int # callback_object
timestamp: int timestamp: int
symbol: str symbol: str
@ -410,6 +492,9 @@ class TrCallbackSymbol:
@dataclass @dataclass
class TrTopic: class TrTopic:
"""
The representation of a ROS 2 topic, linking publishers and subscriptions.
"""
name: str name: str
_c: TrContext = field(repr=False) _c: TrContext = field(repr=False)
timestamp: int = 0 timestamp: int = 0