diff --git a/tracetools_analysis/analysis/sample_data/converted_pingpong b/tracetools_analysis/analysis/sample_data/converted_pingpong index 12b7692..ab8f88c 100644 Binary files a/tracetools_analysis/analysis/sample_data/converted_pingpong and b/tracetools_analysis/analysis/sample_data/converted_pingpong differ diff --git a/tracetools_analysis/tracetools_analysis/data_model/ros.py b/tracetools_analysis/tracetools_analysis/data_model/ros.py index e1064db..fbf2cb6 100644 --- a/tracetools_analysis/tracetools_analysis/data_model/ros.py +++ b/tracetools_analysis/tracetools_analysis/data_model/ros.py @@ -56,6 +56,10 @@ class RosDataModel(DataModel): 'topic_name', 'depth']) self.subscriptions.set_index(['subscription_handle'], inplace=True, drop=True) + self.subscription_objects = pd.DataFrame(columns=['subscription', + 'timestamp', + 'subscription_handle']) + self.subscription_objects.set_index(['subscription'], inplace=True, drop=True) self.services = pd.DataFrame(columns=['service_handle', 'timestamp', 'node_handle', @@ -74,10 +78,10 @@ class RosDataModel(DataModel): 'tid']) self.timers.set_index(['timer_handle'], inplace=True, drop=True) - self.callback_objects = pd.DataFrame(columns=['handle', + self.callback_objects = pd.DataFrame(columns=['reference', 'timestamp', 'callback_object']) - self.callback_objects.set_index(['handle'], inplace=True, drop=True) + self.callback_objects.set_index(['reference'], inplace=True, drop=True) self.callback_symbols = pd.DataFrame(columns=['callback_object', 'timestamp', 'symbol']) @@ -104,11 +108,16 @@ class RosDataModel(DataModel): ) -> None: self.publishers.loc[handle] = [timestamp, node_handle, rmw_handle, topic_name, depth] - def add_subscription( + def add_rcl_subscription( self, handle, timestamp, node_handle, rmw_handle, topic_name, depth ) -> None: self.subscriptions.loc[handle] = [timestamp, node_handle, rmw_handle, topic_name, depth] + def add_rclcpp_subscription( + self, subscription_pointer, timestamp, subscription_handle + ) -> None: + self.subscription_objects.loc[subscription_pointer] = [timestamp, subscription_handle] + def add_service( self, handle, timestamp, node_handle, rmw_handle, service_name ) -> None: @@ -125,9 +134,9 @@ class RosDataModel(DataModel): self.timers.loc[handle] = [timestamp, period, tid] def add_callback_object( - self, handle, timestamp, callback_object + self, reference, timestamp, callback_object ) -> None: - self.callback_objects.loc[handle] = [timestamp, callback_object] + self.callback_objects.loc[reference] = [timestamp, callback_object] def add_callback_symbol( self, callback_object, timestamp, symbol @@ -156,6 +165,8 @@ class RosDataModel(DataModel): print() print(f'Subscriptions:\n{self.subscriptions.to_string()}') print() + print(f'Subscription objects:\n{self.subscription_objects.to_string()}') + print() print(f'Services:\n{self.services.to_string()}') print() print(f'Clients:\n{self.clients.to_string()}') diff --git a/tracetools_analysis/tracetools_analysis/processor/ros2.py b/tracetools_analysis/tracetools_analysis/processor/ros2.py index 914086b..edec9e0 100644 --- a/tracetools_analysis/tracetools_analysis/processor/ros2.py +++ b/tracetools_analysis/tracetools_analysis/processor/ros2.py @@ -43,7 +43,9 @@ class Ros2Handler(EventHandler): 'ros2:rcl_publisher_init': self._handle_rcl_publisher_init, 'ros2:rcl_subscription_init': - self._handle_subscription_init, + self._handle_rcl_subscription_init, + 'ros2:rclcpp_subscription_init': + self._handle_rclcpp_subscription_init, 'ros2:rclcpp_subscription_callback_added': self._handle_rclcpp_subscription_callback_added, 'ros2:rcl_service_init': @@ -78,7 +80,7 @@ class Ros2Handler(EventHandler): return self._data_model def _handle_rcl_init( - self, event: Dict, metadata: EventMetadata + self, event: Dict, metadata: EventMetadata, ) -> None: context_handle = get_field(event, 'context_handle') timestamp = metadata.timestamp @@ -87,7 +89,7 @@ class Ros2Handler(EventHandler): self.data.add_context(context_handle, timestamp, pid, version) def _handle_rcl_node_init( - self, event: Dict, metadata: EventMetadata + self, event: Dict, metadata: EventMetadata, ) -> None: handle = get_field(event, 'node_handle') timestamp = metadata.timestamp @@ -98,7 +100,7 @@ class Ros2Handler(EventHandler): self.data.add_node(handle, timestamp, tid, rmw_handle, name, namespace) def _handle_rcl_publisher_init( - self, event: Dict, metadata: EventMetadata + self, event: Dict, metadata: EventMetadata, ) -> None: handle = get_field(event, 'publisher_handle') timestamp = metadata.timestamp @@ -108,8 +110,8 @@ class Ros2Handler(EventHandler): depth = get_field(event, 'queue_depth') self.data.add_publisher(handle, timestamp, node_handle, rmw_handle, topic_name, depth) - def _handle_subscription_init( - self, event: Dict, metadata: EventMetadata + def _handle_rcl_subscription_init( + self, event: Dict, metadata: EventMetadata, ) -> None: handle = get_field(event, 'subscription_handle') timestamp = metadata.timestamp @@ -117,18 +119,28 @@ class Ros2Handler(EventHandler): rmw_handle = get_field(event, 'rmw_subscription_handle') topic_name = get_field(event, 'topic_name') depth = get_field(event, 'queue_depth') - self.data.add_subscription(handle, timestamp, node_handle, rmw_handle, topic_name, depth) + self.data.add_rcl_subscription( + handle, timestamp, node_handle, rmw_handle, topic_name, depth, + ) + + def _handle_rclcpp_subscription_init( + self, event: Dict, metadata: EventMetadata, + ) -> None: + subscription_pointer = get_field(event, 'subscription') + timestamp = metadata.timestamp + handle = get_field(event, 'subscription_handle') + self.data.add_rclcpp_subscription(subscription_pointer, timestamp, handle) def _handle_rclcpp_subscription_callback_added( - self, event: Dict, metadata: EventMetadata + self, event: Dict, metadata: EventMetadata, ) -> None: - handle = get_field(event, 'subscription_handle') + subscription_pointer = get_field(event, 'subscription') timestamp = metadata.timestamp callback_object = get_field(event, 'callback') - self.data.add_callback_object(handle, timestamp, callback_object) + self.data.add_callback_object(subscription_pointer, timestamp, callback_object) def _handle_rcl_service_init( - self, event: Dict, metadata: EventMetadata + self, event: Dict, metadata: EventMetadata, ) -> None: handle = get_field(event, 'service_handle') timestamp = metadata.timestamp @@ -138,7 +150,7 @@ class Ros2Handler(EventHandler): self.data.add_service(handle, timestamp, node_handle, rmw_handle, service_name) def _handle_rclcpp_service_callback_added( - self, event: Dict, metadata: EventMetadata + self, event: Dict, metadata: EventMetadata, ) -> None: handle = get_field(event, 'service_handle') timestamp = metadata.timestamp @@ -146,7 +158,7 @@ class Ros2Handler(EventHandler): self.data.add_callback_object(handle, timestamp, callback_object) def _handle_rcl_client_init( - self, event: Dict, metadata: EventMetadata + self, event: Dict, metadata: EventMetadata, ) -> None: handle = get_field(event, 'client_handle') timestamp = metadata.timestamp @@ -156,7 +168,7 @@ class Ros2Handler(EventHandler): self.data.add_client(handle, timestamp, node_handle, rmw_handle, service_name) def _handle_rcl_timer_init( - self, event: Dict, metadata: EventMetadata + self, event: Dict, metadata: EventMetadata, ) -> None: handle = get_field(event, 'timer_handle') timestamp = metadata.timestamp @@ -165,7 +177,7 @@ class Ros2Handler(EventHandler): self.data.add_timer(handle, timestamp, period, tid) def _handle_rclcpp_timer_callback_added( - self, event: Dict, metadata: EventMetadata + self, event: Dict, metadata: EventMetadata, ) -> None: handle = get_field(event, 'timer_handle') timestamp = metadata.timestamp @@ -173,7 +185,7 @@ class Ros2Handler(EventHandler): self.data.add_callback_object(handle, timestamp, callback_object) def _handle_rclcpp_callback_register( - self, event: Dict, metadata: EventMetadata + self, event: Dict, metadata: EventMetadata, ) -> None: callback_object = get_field(event, 'callback') timestamp = metadata.timestamp @@ -181,14 +193,14 @@ class Ros2Handler(EventHandler): self.data.add_callback_symbol(callback_object, timestamp, symbol) def _handle_callback_start( - self, event: Dict, metadata: EventMetadata + self, event: Dict, metadata: EventMetadata, ) -> None: # Add to dict callback_addr = get_field(event, 'callback') self._callback_instances[callback_addr] = (event, metadata) def _handle_callback_end( - self, event: Dict, metadata: EventMetadata + self, event: Dict, metadata: EventMetadata, ) -> None: # Fetch from dict callback_object = get_field(event, 'callback') diff --git a/tracetools_analysis/tracetools_analysis/utils.py b/tracetools_analysis/tracetools_analysis/utils.py index d977900..c8ad1c8 100644 --- a/tracetools_analysis/tracetools_analysis/utils.py +++ b/tracetools_analysis/tracetools_analysis/utils.py @@ -38,7 +38,10 @@ class DataModelUtil(): This class provides basic util functions. """ - def __init__(self, data_model: DataModel) -> None: + def __init__( + self, + data_model: DataModel, + ) -> None: """ Constructor. @@ -105,7 +108,10 @@ class DataModelUtil(): class ProfileDataModelUtil(DataModelUtil): """Profiling data model utility class.""" - def __init__(self, data_model: ProfileDataModel) -> None: + def __init__( + self, + data_model: ProfileDataModel, + ) -> None: """ Constructor. @@ -113,14 +119,20 @@ class ProfileDataModelUtil(DataModelUtil): """ super().__init__(data_model) - def with_tid(self, tid: int) -> DataFrame: + def with_tid( + self, + tid: int, + ) -> DataFrame: return self.data.times.loc[self.data.times['tid'] == tid] def get_tids(self) -> Set[int]: """Get the TIDs in the data model.""" return set(self.data.times['tid']) - def get_call_tree(self, tid: int) -> Dict[str, List[str]]: + def get_call_tree( + self, + tid: int, + ) -> Dict[str, List[str]]: depth_names = self.with_tid(tid)[ ['depth', 'function_name', 'parent_name'] ].drop_duplicates() @@ -136,7 +148,10 @@ class ProfileDataModelUtil(DataModelUtil): tree[parent].add(name) return dict(tree) - def get_function_duration_data(self, tid: int) -> List[Dict[str, Union[int, str, DataFrame]]]: + def get_function_duration_data( + self, + tid: int, + ) -> List[Dict[str, Union[int, str, DataFrame]]]: """Get duration data for each function.""" tid_df = self.with_tid(tid) depth_names = tid_df[['depth', 'function_name', 'parent_name']].drop_duplicates() @@ -167,7 +182,10 @@ class ProfileDataModelUtil(DataModelUtil): class CpuTimeDataModelUtil(DataModelUtil): """CPU time data model utility class.""" - def __init__(self, data_model: CpuTimeDataModel) -> None: + def __init__( + self, + data_model: CpuTimeDataModel, + ) -> None: """ Constructor. @@ -183,7 +201,10 @@ class CpuTimeDataModelUtil(DataModelUtil): class RosDataModelUtil(DataModelUtil): """ROS data model utility class.""" - def __init__(self, data_model: RosDataModel) -> None: + def __init__( + self, + data_model: RosDataModel, + ) -> None: """ Constructor. @@ -191,7 +212,10 @@ class RosDataModelUtil(DataModelUtil): """ super().__init__(data_model) - def _prettify(self, original: str) -> str: + def _prettify( + self, + original: str, + ) -> str: """ Process symbol to make it more readable. @@ -263,7 +287,8 @@ class RosDataModelUtil(DataModelUtil): } def get_callback_durations( - self, callback_obj: int + self, + callback_obj: int, ) -> DataFrame: """ Get durations of callback instances for a given callback object. @@ -280,7 +305,8 @@ class RosDataModelUtil(DataModelUtil): return self.convert_time_columns(data, ['duration'], ['timestamp']) def get_node_tid_from_name( - self, node_name: str + self, + node_name: str, ) -> Union[int, None]: """ Get the tid corresponding to a node. @@ -296,7 +322,8 @@ class RosDataModelUtil(DataModelUtil): return node_row.iloc[0]['tid'] if not node_row.empty else None def get_node_names_from_tid( - self, tid: str + self, + tid: str, ) -> Union[List[str], None]: """ Get the list of node names corresponding to a tid. @@ -309,7 +336,8 @@ class RosDataModelUtil(DataModelUtil): ]['name'].tolist() def get_callback_owner_info( - self, callback_obj: int + self, + callback_obj: int, ) -> Union[str, None]: """ Get information about the owner of a callback. @@ -322,36 +350,37 @@ class RosDataModelUtil(DataModelUtil): :param callback_obj: the callback object value :return: information about the owner of the callback, or `None` if it fails """ - # Get handle corresponding to callback object - handle = self.data.callback_objects.loc[ + # Get reference corresponding to callback object + reference = self.data.callback_objects.loc[ self.data.callback_objects['callback_object'] == callback_obj ].index.values.astype(int)[0] type_name = None info = None # Check if it's a timer first (since it's slightly different than the others) - if handle in self.data.timers.index: + if reference in self.data.timers.index: type_name = 'Timer' - info = self.get_timer_handle_info(handle) - elif handle in self.data.publishers.index: + info = self.get_timer_handle_info(reference) + elif reference in self.data.publishers.index: type_name = 'Publisher' - info = self.get_publisher_handle_info(handle) - elif handle in self.data.subscriptions.index: + info = self.get_publisher_handle_info(reference) + elif reference in self.data.subscription_objects.index: type_name = 'Subscription' - info = self.get_subscription_handle_info(handle) - elif handle in self.data.services.index: + info = self.get_subscription_reference_info(reference) + elif reference in self.data.services.index: type_name = 'Service' - info = self.get_subscription_handle_info(handle) - elif handle in self.data.clients.index: + info = self.get_service_handle_info(reference) + elif reference in self.data.clients.index: type_name = 'Client' - info = self.get_client_handle_info(handle) + info = self.get_client_handle_info(reference) if info is not None: info = f'{type_name} -- {self.format_info_dict(info)}' return info def get_timer_handle_info( - self, timer_handle: int + self, + timer_handle: int, ) -> Union[Mapping[str, Any], None]: """ Get information about the owner of a timer. @@ -369,7 +398,8 @@ class RosDataModelUtil(DataModelUtil): return {'tid': tid, 'period': f'{period_ms:.0f} ms'} def get_publisher_handle_info( - self, publisher_handle: int + self, + publisher_handle: int, ) -> Union[Mapping[str, Any], None]: """ Get information about a publisher handle. @@ -386,30 +416,61 @@ class RosDataModelUtil(DataModelUtil): publisher_info = {'topic': topic_name} return {**node_handle_info, **publisher_info} - def get_subscription_handle_info( - self, subscription_handle: int + def get_subscription_reference_info( + self, + subscription_reference: int, ) -> Union[Mapping[str, Any], None]: """ Get information about a subscription handle. - :param subscription_handle: the subscription handle value + :param subscription_reference: the subscription reference value :return: a dictionary with name:value info, or `None` if it fails """ - subscriptions_info = self.data.subscriptions.merge( - self.data.nodes, - left_on='node_handle', - right_index=True) - if subscription_handle not in self.data.subscriptions.index: + # First check that the subscription reference exists + if subscription_reference not in self.data.subscription_objects.index: return None - node_handle = subscriptions_info.loc[subscription_handle, 'node_handle'] + # To get information about a subscription reference, we need 3 dataframes + # * subscription_objects + # * subscription (reference) <--> subscription_handle + # * subscriptions + # * subscription_handle <--> topic_name + # * subscription_handle <--> node_handle + # * nodes + # * node_handle <--> (node info) + # First, drop unnecessary common columns for debugging simplicity + subscription_objects_simple = self.data.subscription_objects.drop( + columns=['timestamp'], + axis=1, + ) + subscriptions_simple = self.data.subscriptions.drop( + columns=['timestamp', 'rmw_handle'], + inplace=False, + ) + nodes_simple = self.data.nodes.drop( + columns=['timestamp', 'rmw_handle'], + inplace=False, + ) + # Then merge the 3 dataframes + subscriptions_info = subscription_objects_simple.merge( + subscriptions_simple, + left_on='subscription_handle', + right_index=True, + ).merge( + nodes_simple, + left_on='node_handle', + right_index=True, + ) + + node_handle = subscriptions_info.loc[subscription_reference, 'node_handle'] node_handle_info = self.get_node_handle_info(node_handle) - topic_name = subscriptions_info.loc[subscription_handle, 'topic_name'] + topic_name = subscriptions_info.loc[subscription_reference, 'topic_name'] subscription_info = {'topic': topic_name} return {**node_handle_info, **subscription_info} def get_service_handle_info( - self, service_handle: int + self, + service_handle: int, ) -> Union[Mapping[str, Any], None]: """ Get information about a service handle. @@ -427,7 +488,8 @@ class RosDataModelUtil(DataModelUtil): return {**node_handle_info, **service_info} def get_client_handle_info( - self, client_handle: int + self, + client_handle: int, ) -> Union[Mapping[str, Any], None]: """ Get information about a client handle. @@ -445,7 +507,8 @@ class RosDataModelUtil(DataModelUtil): return {**node_handle_info, **service_info} def get_node_handle_info( - self, node_handle: int + self, + node_handle: int, ) -> Union[Mapping[str, Any], None]: """ Get information about a node handle. @@ -460,5 +523,8 @@ class RosDataModelUtil(DataModelUtil): tid = self.data.nodes.loc[node_handle, 'tid'] return {'node': node_name, 'tid': tid} - def format_info_dict(self, info_dict: Mapping[str, Any]) -> str: + def format_info_dict( + self, + info_dict: Mapping[str, Any], + ) -> str: return ', '.join([f'{key}: {val}' for key, val in info_dict.items()])