From 78735ef06706a33b64565e71fc150f03b751f5f2 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Sun, 24 Nov 2024 11:40:04 -0800 Subject: [PATCH] ros2: support client/service instrumentation This adds support for the new client/service (i.e., RPC) instrumentation in ROS 2, see https://github.com/ros2/ros2_tracing/pull/145. 1. In the objects analysis, create client and service objects 2. In the messages analysis, create the following instances: 1. Request publication 2. Request take and callback 3. Response publication 4. Response take 5. Message transport for requests and responses 3. In the messages dataprovider, display the above instances There is one limitation. Normal message publications and message takes have instrumentation that provides a "start time." For example, for message publications, the `ros2:rclcpp_publish` tracepoint is the start and the `ros2:rmw_publish` tracepoint is the end of a message publication. This allows us to attribute a duration to the publication and therefore display a time graph state. However, we only have a single tracepoint for client/service-related publication/take instances, so we do not have any duration data. For now, just hardcode a 5000 ns duration so that time graph states are visible enough. Signed-off-by: Christophe Bedard --- .../internal/ros2/core/Activator.java | 4 + .../Ros2ObjectTimeGraphEntryModel.java | 6 + .../Ros2ObjectTimeGraphEntryModelType.java | 4 + .../messages/Ros2MessagesDataProvider.java | 117 ++++++++- .../messages/Ros2MessagesStateProvider.java | 235 +++++++++++++++++- .../analysis/messages/Ros2MessagesUtil.java | 151 +++++++++++ .../objects/Ros2ObjectsStateProvider.java | 90 ++++++- .../analysis/objects/Ros2ObjectsUtil.java | 164 ++++++++++++ .../Ros2MessageTransportInstance.java | 2 +- .../ros2/core/model/messages/Ros2Request.java | 83 +++++++ .../core/model/messages/Ros2Response.java | 82 ++++++ .../core/model/objects/Ros2CallbackType.java | 7 +- .../core/model/objects/Ros2ClientObject.java | 80 ++++++ .../core/model/objects/Ros2ServiceObject.java | 103 ++++++++ .../core/trace/layout/IRos2EventLayout.java | 29 +++ .../trace/layout/Ros2RollingEventLayout.java | 64 +++++ .../ui/views/Ros2ObjectTreeLabelProvider.java | 6 + .../Ros2MessagesPresentationProvider.java | 50 ++++ 18 files changed, 1253 insertions(+), 24 deletions(-) create mode 100644 tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/messages/Ros2Request.java create mode 100644 tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/messages/Ros2Response.java create mode 100644 tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/objects/Ros2ClientObject.java create mode 100644 tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/objects/Ros2ServiceObject.java diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/Activator.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/Activator.java index f8da8da9b..7229a94e2 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/Activator.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/Activator.java @@ -22,9 +22,11 @@ import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2TakeInstance; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2TimerCallbackInstance; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2CallbackObject; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ClientObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2NodeObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ObjectHandle; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2PublisherObject; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ServiceObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2SubscriptionObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2TimerObject; import org.eclipse.tracecompass.internal.provisional.statesystem.core.statevalue.CustomStateValue; @@ -65,6 +67,8 @@ protected void startActions() { CustomStateValue.registerCustomFactory(Ros2SubscriptionObject.CUSTOM_TYPE_ID, Ros2SubscriptionObject.ROS2_SUBSCRIPTION_OBJECT_VALUE_FACTORY); CustomStateValue.registerCustomFactory(Ros2TimerObject.CUSTOM_TYPE_ID, Ros2TimerObject.ROS2_TIMER_OBJECT_VALUE_FACTORY); CustomStateValue.registerCustomFactory(Ros2CallbackObject.CUSTOM_TYPE_ID, Ros2CallbackObject.ROS2_CALLBACK_OBJECT_VALUE_FACTORY); + CustomStateValue.registerCustomFactory(Ros2ClientObject.CUSTOM_TYPE_ID, Ros2ClientObject.ROS2_CLIENT_OBJECT_VALUE_FACTORY); + CustomStateValue.registerCustomFactory(Ros2ServiceObject.CUSTOM_TYPE_ID, Ros2ServiceObject.ROS2_SERVICE_OBJECT_VALUE_FACTORY); // Instances (for messages analysis) CustomStateValue.registerCustomFactory(Ros2PubInstance.CUSTOM_TYPE_ID, Ros2PubInstance.ROS2_PUB_INSTANCE_VALUE_FACTORY); CustomStateValue.registerCustomFactory(Ros2TakeInstance.CUSTOM_TYPE_ID, Ros2TakeInstance.ROS2_TAKE_INSTANCE_VALUE_FACTORY); diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/Ros2ObjectTimeGraphEntryModel.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/Ros2ObjectTimeGraphEntryModel.java index a17171c3a..6a7d12a25 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/Ros2ObjectTimeGraphEntryModel.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/Ros2ObjectTimeGraphEntryModel.java @@ -16,8 +16,10 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ClientObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2NodeObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2PublisherObject; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ServiceObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2SubscriptionObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2TimerObject; import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphEntryModel; @@ -72,6 +74,10 @@ private static String getEntryModelName(Ros2ObjectTimeGraphEntryModelType type, return ((Ros2PublisherObject) object).getTopicName(); case SUBSCRIPTION: return ((Ros2SubscriptionObject) object).getTopicName(); + case CLIENT: + return ((Ros2ClientObject) object).getTopicName(); + case SERVICE: + return ((Ros2ServiceObject) object).getTopicName(); case TIMER: return getTimerPeriodAsString((Ros2TimerObject) object); default: diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/Ros2ObjectTimeGraphEntryModelType.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/Ros2ObjectTimeGraphEntryModelType.java index b4a249162..4db4baac1 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/Ros2ObjectTimeGraphEntryModelType.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/Ros2ObjectTimeGraphEntryModelType.java @@ -25,6 +25,10 @@ public enum Ros2ObjectTimeGraphEntryModelType { PUBLISHER, /** Subscription */ SUBSCRIPTION, + /** Client */ + CLIENT, + /** Service */ + SERVICE, /** Timer */ TIMER; } diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/messages/Ros2MessagesDataProvider.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/messages/Ros2MessagesDataProvider.java index b76d355d3..4b8fa2719 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/messages/Ros2MessagesDataProvider.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/messages/Ros2MessagesDataProvider.java @@ -19,6 +19,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Predicate; import org.apache.commons.lang3.StringUtils; @@ -37,9 +38,11 @@ import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2SubCallbackInstance; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2TakeInstance; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2TimerCallbackInstance; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ClientObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2NodeObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ObjectHandle; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2PublisherObject; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ServiceObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2SubscriptionObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2TimerObject; import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils; @@ -108,7 +111,7 @@ public Ros2MessagesDataProvider(@NonNull ITmfTrace trace, @NonNull Ros2MessagesA * @author Christophe Bedard */ public enum ArrowType { - /** Transport link (pub->sub over network) */ + /** Transport link (pub->sub or request/response over network) */ TRANSPORT(1), /** Callback-publication link */ CALLBACK_PUB(2), @@ -206,7 +209,7 @@ public int getId() { if (monitor != null && monitor.isCanceled()) { return new TimeGraphModel(Collections.emptyList()); } - addRows(rows, entry, intervals, predicates, monitor); + addRows(ss, rows, entry, intervals, predicates, monitor); } return new TimeGraphModel(rows); } @@ -236,12 +239,39 @@ private static void queryIntervals(@NonNull ITmfStateSystem ss, TreeMultimap rows, Map.Entry<@NonNull Long, @NonNull Integer> entry, TreeMultimap intervals, - @NonNull Map<@NonNull Integer, @NonNull Predicate<@NonNull Multimap<@NonNull String, @NonNull Object>>> predicates, @Nullable IProgressMonitor monitor) { + private void addRows(@NonNull ITmfStateSystem ss, List<@NonNull ITimeGraphRowModel> rows, Map.Entry<@NonNull Long, @NonNull Integer> entry, TreeMultimap intervals, + @NonNull Map<@NonNull Integer, @NonNull Predicate<@NonNull Multimap<@NonNull String, @NonNull Object>>> predicates, @Nullable IProgressMonitor monitor) throws StateSystemDisposedException { List<@NonNull ITimeGraphState> eventList = new ArrayList<>(); for (ITmfStateInterval interval : intervals.get(entry.getValue())) { addRow(entry, predicates, monitor, eventList, interval); } + + /** + * State system intervals for clients & services are stored under two + * attributes (send & take). However, an entry only corresponds to one + * attribute. Since we create entry models for clients & services using + * the "take" attribute, we need to do a simple workaround here to also + * create time graph states for state system intervals under the "send" + * attribute in the same client/service entry model. + */ + int quark = Objects.requireNonNull(entry.getValue()); + String name = ss.getAttributeName(quark); + int parentQuark = ss.getParentAttributeQuark(quark); + int grandParentQuark = ss.getParentAttributeQuark(parentQuark); + String grandParentName = grandParentQuark != ITmfStateSystem.ROOT_ATTRIBUTE ? ss.getAttributeName(grandParentQuark) : StringUtils.EMPTY; + // If this is an entry for a "take" attribute + if (name.equals(Ros2MessagesUtil.ClientServiceInstanceType.TAKE.toString()) && + (grandParentName.equals(Ros2MessagesUtil.LIST_CLIENTS) || grandParentName.equals(Ros2MessagesUtil.LIST_SERVICES))) { + // Find quark for "send" attribute + int clientSendQuark = ss.optQuarkRelative(parentQuark, Ros2MessagesUtil.ClientServiceInstanceType.SEND.toString()); + if (ITmfStateSystem.INVALID_ATTRIBUTE != clientSendQuark) { + // Create time graph states under the same ("take") entry model + for (ITmfStateInterval interval : ss.query2D(Collections.singleton(clientSendQuark), ss.getStartTime(), ss.getCurrentEndTime())) { + addRow(entry, predicates, monitor, eventList, interval); + } + } + } + rows.add(new TimeGraphRowModel(entry.getKey(), eventList)); } @@ -264,7 +294,7 @@ private void addRow(Map.Entry<@NonNull Long, @NonNull Integer> entry, @NonNull M fHandleToIdMap.put(pubInstance.getPublisherHandle(), entry.getKey()); } else if (valObject instanceof Ros2SubCallbackInstance) { - // Subscription callback + // Subscription callback or service request callback Ros2SubCallbackInstance subCallbackInstance = (Ros2SubCallbackInstance) valObject; Ros2TakeInstance takeInstance = subCallbackInstance.getTakeInstance(); @@ -275,6 +305,13 @@ private void addRow(Map.Entry<@NonNull Long, @NonNull Integer> entry, @NonNull M Ros2CallbackTimeGraphState callbackState = new Ros2CallbackTimeGraphState(callbackInstance); applyFilterAndAddState(eventList, callbackState, entry.getKey(), predicates, monitor); + fHandleToIdMap.put(takeInstance.getSubscriptionHandle(), entry.getKey()); + } else if (valObject instanceof Ros2TakeInstance) { + // Client response take + Ros2TakeInstance takeInstance = (Ros2TakeInstance) valObject; + Ros2TakeTimeGraphState takeState = new Ros2TakeTimeGraphState(takeInstance); + applyFilterAndAddState(eventList, takeState, entry.getKey(), predicates, monitor); + fHandleToIdMap.put(takeInstance.getSubscriptionHandle(), entry.getKey()); } else if (valObject instanceof Ros2TimerCallbackInstance) { // Timer callback @@ -311,6 +348,8 @@ private void addChildrenEntryModel(ITmfStateSystem ss, Builder<@NonNull TimeGrap long childId = getId(child); String name = ss.getAttributeName(child); String parentName = quark != ITmfStateSystem.ROOT_ATTRIBUTE ? ss.getAttributeName(quark) : StringUtils.EMPTY; + int grandParentQuark = ss.getParentAttributeQuark(quark); + String grandParentName = grandParentQuark != ITmfStateSystem.ROOT_ATTRIBUTE ? ss.getAttributeName(grandParentQuark) : StringUtils.EMPTY; if (ITmfStateSystem.ROOT_ATTRIBUTE == quark) { if (addEntryModel(ss, builder, childId, parentId, child, Ros2ObjectTimeGraphEntryModelType.TRACE)) { addChildren(ss, builder, child, childId); @@ -323,9 +362,19 @@ private void addChildrenEntryModel(ITmfStateSystem ss, Builder<@NonNull TimeGrap addEntryModel(ss, builder, childId, parentId, child, Ros2ObjectTimeGraphEntryModelType.PUBLISHER); } else if (parentName.equals(Ros2MessagesUtil.LIST_SUBSCRIPTIONS)) { addEntryModel(ss, builder, childId, parentId, child, Ros2ObjectTimeGraphEntryModelType.SUBSCRIPTION); + } else if (name.equals(Ros2MessagesUtil.ClientServiceInstanceType.TAKE.toString())) { + // Only use the "take" attribute as the entry model + if (grandParentName.equals(Ros2MessagesUtil.LIST_CLIENTS)) { + addEntryModel(ss, builder, childId, parentId, child, Ros2ObjectTimeGraphEntryModelType.CLIENT); + } else if (grandParentName.equals(Ros2MessagesUtil.LIST_SERVICES)) { + addEntryModel(ss, builder, childId, parentId, child, Ros2ObjectTimeGraphEntryModelType.SERVICE); + } } else if (parentName.equals(Ros2MessagesUtil.LIST_TIMERS)) { addEntryModel(ss, builder, childId, parentId, child, Ros2ObjectTimeGraphEntryModelType.TIMER); - } else if (name.equals(Ros2MessagesUtil.LIST_NODES) || name.equals(Ros2MessagesUtil.LIST_PUBLISHERS) || name.equals(Ros2MessagesUtil.LIST_SUBSCRIPTIONS) || name.equals(Ros2MessagesUtil.LIST_TIMERS)) { + } else if (name.equals(Ros2MessagesUtil.LIST_NODES) || name.equals(Ros2MessagesUtil.LIST_PUBLISHERS) || name.equals(Ros2MessagesUtil.LIST_SUBSCRIPTIONS) || + name.equals(Ros2MessagesUtil.LIST_CLIENTS) || parentName.equals(Ros2MessagesUtil.LIST_CLIENTS) || + name.equals(Ros2MessagesUtil.LIST_SERVICES) || parentName.equals(Ros2MessagesUtil.LIST_SERVICES) || + name.equals(Ros2MessagesUtil.ClientServiceInstanceType.SEND.toString()) || name.equals(Ros2MessagesUtil.LIST_TIMERS)) { /** * Skip this attribute: don't add an entry model, but do proceed * with children, effectively skipping a layer in the state system @@ -372,6 +421,22 @@ private boolean addEntryModel(ITmfStateSystem ss, Builder<@NonNull TimeGraphEntr return true; } break; + case CLIENT: + @Nullable + Ros2ClientObject clientObject = getClientObject(ss, quark); + if (null != clientObject) { + builder.add(new Ros2ObjectTimeGraphEntryModel(id, parentId, ss.getStartTime(), ss.getCurrentEndTime(), Ros2ObjectTimeGraphEntryModelType.CLIENT, clientObject)); + return true; + } + break; + case SERVICE: + @Nullable + Ros2ServiceObject serviceObject = getServiceObject(ss, quark); + if (null != serviceObject) { + builder.add(new Ros2ObjectTimeGraphEntryModel(id, parentId, ss.getStartTime(), ss.getCurrentEndTime(), Ros2ObjectTimeGraphEntryModelType.SERVICE, serviceObject)); + return true; + } + break; case TIMER: @Nullable Ros2TimerObject timerObject = getTimerObject(ss, quark); @@ -459,6 +524,46 @@ private boolean addEntryModel(ITmfStateSystem ss, Builder<@NonNull TimeGraphEntr return null; } + private @Nullable Ros2ClientObject getClientObject(ITmfStateSystem ss, int quark) { + try { + // Get client handle from a time graph state + Iterable<@NonNull ITmfStateInterval> query2d = ss.query2D(Collections.singleton(quark), ss.getStartTime(), ss.getCurrentEndTime()); + for (ITmfStateInterval iTmfStateInterval : query2d) { + if(iTmfStateInterval.getValue() instanceof Ros2TakeInstance) { + @Nullable + Ros2TakeInstance responseTakeInstance = (Ros2TakeInstance) iTmfStateInterval.getValue(); + if (null != responseTakeInstance) { + return Ros2ObjectsUtil.getClientObjectFromHandle(fObjectsSs, ss.getCurrentEndTime(), responseTakeInstance.getSubscriptionHandle()); + } + } + } + } catch (IndexOutOfBoundsException | TimeRangeException | StateSystemDisposedException e) { + // Do nothing + } + Activator.getInstance().logError("could not get client object for entry model"); //$NON-NLS-1$ + return null; + } + + private @Nullable Ros2ServiceObject getServiceObject(ITmfStateSystem ss, int quark) { + try { + // Get service handle from a time graph state + Iterable<@NonNull ITmfStateInterval> query2d = ss.query2D(Collections.singleton(quark), ss.getStartTime(), ss.getCurrentEndTime()); + for (ITmfStateInterval iTmfStateInterval : query2d) { + if (iTmfStateInterval.getValue() instanceof Ros2SubCallbackInstance) { + @Nullable + Ros2SubCallbackInstance subCallbackInstance = (Ros2SubCallbackInstance) iTmfStateInterval.getValue(); + if (null != subCallbackInstance) { + return Ros2ObjectsUtil.getServiceObjectFromHandle(fObjectsSs, ss.getCurrentEndTime(), subCallbackInstance.getTakeInstance().getSubscriptionHandle()); + } + } + } + } catch (IndexOutOfBoundsException | TimeRangeException | StateSystemDisposedException e) { + // Do nothing + } + Activator.getInstance().logError("could not get service object for entry model"); //$NON-NLS-1$ + return null; + } + private @Nullable Ros2TimerObject getTimerObject(ITmfStateSystem ss, int quark) { try { // Get timer handle from a time graph state diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/messages/Ros2MessagesStateProvider.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/messages/Ros2MessagesStateProvider.java index 14da04893..2bdef64aa 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/messages/Ros2MessagesStateProvider.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/messages/Ros2MessagesStateProvider.java @@ -22,6 +22,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.tracecompass.incubator.internal.ros2.core.Activator; import org.eclipse.tracecompass.incubator.internal.ros2.core.analysis.AbstractRos2StateProvider; +import org.eclipse.tracecompass.incubator.internal.ros2.core.analysis.messages.Ros2MessagesUtil.ClientServiceInstanceType; import org.eclipse.tracecompass.incubator.internal.ros2.core.analysis.objects.Ros2ObjectsUtil; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.HostProcessPointer; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.HostThread; @@ -30,10 +31,14 @@ import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2MessageTimestamp; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2MessageTransportInstance; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2PubInstance; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2Request; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2Response; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2SubCallbackInstance; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2TakeInstance; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2TimerCallbackInstance; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Gid; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2CallbackType; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ClientObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2NodeObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ObjectHandle; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2PublisherObject; @@ -78,6 +83,14 @@ public class Ros2MessagesStateProvider extends AbstractRos2StateProvider { private Multimap> fCallbackPublications = MultimapBuilder.hashKeys().arrayListValues().build(); // Pub-sub links private Map> fPublications = Maps.newHashMap(); + // Requests + private Map fClientHandles = Maps.newHashMap(); + // Responses + private Map fServiceHandles = Maps.newHashMap(); + // Request links + private Map> fRequests = Maps.newHashMap(); + // Response links + private Map> fResponses = Maps.newHashMap(); /** * Constructor @@ -124,6 +137,7 @@ protected void eventHandle(@NonNull ITmfEvent event) { eventHandlePublish(event, ss, timestamp); eventHandleTake(ss, event, timestamp); eventHandleCallback(event, ss, timestamp); + eventHandleRequestResponse(event, ss, timestamp); } private void eventHandleHelpers(@NonNull ITmfEvent event) { @@ -188,6 +202,23 @@ private void eventHandleHelpers(@NonNull ITmfEvent event) { fKnownRmwPublishers.add(rmwPublisher); } } + + /** + * Create { rmw_{client,service}_handle -> rcl_{client,service}_handle } map for easy lookup + * instead of looking it up in the state system. + */ + // rcl_client_init + if (isEvent(event, LAYOUT.eventRclClientInit())) { + Ros2ObjectHandle rmwClientHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldRmwClientHandle())); + Ros2ObjectHandle clientHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldClientHandle())); + fClientHandles.put(rmwClientHandle, clientHandle); + } + // rcl_service_init + else if (isEvent(event, LAYOUT.eventRclServiceInit())) { + Ros2ObjectHandle rmwServiceHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldRmwServiceHandle())); + Ros2ObjectHandle serviceHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldServiceHandle())); + fServiceHandles.put(rmwServiceHandle, serviceHandle); + } } private void eventHandlePublish(@NonNull ITmfEvent event, ITmfStateSystemBuilder ss, long timestamp) { @@ -319,8 +350,9 @@ private void addPublicationInstance(ITmfStateSystemBuilder ss, long pubTimestamp // Keep pub event for pub-sub links /** - * TODO match using publisher GID when rmw_cyclonedds correctly supports - * it. + * TODO match using publisher GID (and pub sequence number) when + * rmw_cyclonedds correctly supports it. See: + * https://github.com/ros2/rmw_cyclonedds/issues/377. */ Ros2MessageTimestamp messageSourceTimestamp = new Ros2MessageTimestamp(pubInstance.getSourceTimestamp(), publisherObject.getTopicName()); fPublications.put(messageSourceTimestamp, new Pair<>(publisherObject.getHandle(), endPubTimestamp)); @@ -510,6 +542,8 @@ private void eventHandleCallbackEnd(@NonNull ITmfEvent event, ITmfStateSystemBui Ros2CallbackType callbackType = callbackOwnerHandle.getSecond(); if (callbackType.equals(Ros2CallbackType.SUBSCRIPTION)) { addSubscriptionCallback(ss, timestamp, startTimestamp, isIntraProcess, tid, ownerHandle); + } else if (callbackType.equals(Ros2CallbackType.SERVICE)) { + addServiceCallback(ss, timestamp, startTimestamp, isIntraProcess, tid, ownerHandle); } else if (callbackType.equals(Ros2CallbackType.TIMER)) { addTimerCallback(ss, timestamp, startTimestamp, isIntraProcess, tid, ownerHandle); } @@ -540,6 +574,24 @@ private void addSubscriptionCallback(ITmfStateSystemBuilder ss, long timestamp, ss.modifyAttribute(callbackInstance.getEndTime(), null, subQuark); } + private void addServiceCallback(ITmfStateSystemBuilder ss, long timestamp, long startTimestamp, boolean isIntraProcess, long tid, @NonNull Ros2ObjectHandle serviceHandle) { + // Get take instance from map + Ros2TakeInstance takeInstance = fTakeInstances.remove(serviceHandle); + if (null == takeInstance) { + Activator.getInstance().logError("could not find corresponding take instance"); //$NON-NLS-1$ + return; + } + Ros2CallbackInstance callbackInstance = new Ros2CallbackInstance(serviceHandle, tid, isIntraProcess, startTimestamp, timestamp); + Ros2SubCallbackInstance serviceCallbackInstance = new Ros2SubCallbackInstance(takeInstance, callbackInstance); + + Integer serviceCallbackQuark = Ros2MessagesUtil.getServiceQuarkAndAdd(ss, fObjectsSs, timestamp, serviceHandle, ClientServiceInstanceType.TAKE); + if (null == serviceCallbackQuark) { + return; + } + ss.modifyAttribute(takeInstance.getStartTime(), serviceCallbackInstance, serviceCallbackQuark); + ss.modifyAttribute(callbackInstance.getEndTime(), null, serviceCallbackQuark); + } + private void addTimerCallback(ITmfStateSystemBuilder ss, long timestamp, long startTimestamp, boolean isIntraProcess, long tid, @NonNull Ros2ObjectHandle timerHandle) { Ros2CallbackInstance callbackInstance = new Ros2CallbackInstance(timerHandle, tid, isIntraProcess, startTimestamp, timestamp); Ros2TimerCallbackInstance timerCallbackInstance = new Ros2TimerCallbackInstance(timerHandle, callbackInstance); @@ -569,6 +621,185 @@ private void addCallbackPublications(ITmfStateSystemBuilder ss, @NonNull ITmfEve fCallbackPublications.removeAll(hostThread); } + private void eventHandleRequestResponse(@NonNull ITmfEvent event, ITmfStateSystemBuilder ss, long timestamp) { + // rmw_send_request + if (isEvent(event, LAYOUT.eventRmwSendRequest())) { + eventHandleRequestSend(event, ss, timestamp); + } + // rmw_take_request + else if (isEvent(event, LAYOUT.eventRmwTakeRequest())) { + eventHandleRequestTake(event, ss, timestamp); + } + // rmw_send_response + else if (isEvent(event, LAYOUT.eventRmwSendResponse())) { + eventHandleResponseSend(event, ss, timestamp); + } + // rmw_take_response + else if (isEvent(event, LAYOUT.eventRmwTakeResponse())) { + eventHandleResponseTake(event, ss, timestamp); + } + } + + private void eventHandleRequestSend(@NonNull ITmfEvent event, ITmfStateSystemBuilder ss, long timestamp) { + Ros2ObjectHandle rmwClientHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldRmwClientHandle())); + HostProcessPointer request = hostProcessPointerFrom(event, (long) getField(event, LAYOUT.fieldRequest())); + long sequenceNumber = (long) getField(event, LAYOUT.fieldSequenceNumber()); + + // Get client handle and GID + Ros2ObjectHandle clientHandle = fClientHandles.get(rmwClientHandle); + if (null == clientHandle) { + Activator.getInstance().logError("could not find client_handle for rmw_client_handle=" + rmwClientHandle.toString()); //$NON-NLS-1$ + return; + } + Ros2ClientObject clientObject = Ros2ObjectsUtil.getClientObjectFromHandle(fObjectsSs, timestamp, clientHandle); + if (null == clientObject) { + Activator.getInstance().logError("could not find client object for client handle=" + clientHandle.getHandle()); //$NON-NLS-1$ + return; + } + + // Create request instance + Integer clientPubQuark = Ros2MessagesUtil.getClientQuarkAndAdd(ss, fObjectsSs, timestamp, clientHandle, ClientServiceInstanceType.SEND); + if (null == clientPubQuark) { + return; + } + HostThread thread = hostThreadFrom(event); + Ros2PubInstance requestPubInstance = new Ros2PubInstance(clientHandle, thread.getTid(), request, timestamp); + // TODO figure out proper request publication start timestamp + long requestPubStartTime = timestamp - 5000; + long requestPubEndTime = timestamp; + ss.modifyAttribute(requestPubStartTime, requestPubInstance, clientPubQuark); + ss.modifyAttribute(requestPubEndTime, null, clientPubQuark); + + // Save for request send/take matching + Ros2Request requestInfo = new Ros2Request(clientObject.getGid(), sequenceNumber); + fRequests.put(requestInfo, new Pair<>(clientHandle, timestamp)); + } + + private void eventHandleRequestTake(@NonNull ITmfEvent event, ITmfStateSystemBuilder ss, long timestamp) { + Ros2ObjectHandle rmwServiceHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldRmwServiceHandle())); + HostProcessPointer request = hostProcessPointerFrom(event, (long) getField(event, LAYOUT.fieldRequest())); + long[] clientGidArray = (long[]) getField(event, LAYOUT.fieldClientGid()); + long sequenceNumber = (long) getField(event, LAYOUT.fieldSequenceNumber()); + + Ros2ObjectHandle serviceHandle = fServiceHandles.get(rmwServiceHandle); + if (null == serviceHandle) { + Activator.getInstance().logError("could not find service_handle for rmw_service_handle=" + rmwServiceHandle.toString()); //$NON-NLS-1$ + return; + } + + /* + * Get request event without removing from map, since the same request + * can be received by more than 1 service. + * + * TODO drop elements from map after some time, in case the map gets + * too big? + */ + Gid clientGid = new Gid(clientGidArray); + Ros2Request requestInfo = new Ros2Request(clientGid, sequenceNumber); + Pair<@NonNull Ros2ObjectHandle, @NonNull Long> requestSourceInfo = fRequests.get(requestInfo); + if (null == requestSourceInfo) { + Activator.getInstance().logError("could not find corresponding request=" + requestInfo.toString()); //$NON-NLS-1$ + return; + } + Ros2ObjectHandle clientHandle = requestSourceInfo.getFirst(); + long sourcePubTimestamp = requestSourceInfo.getSecond(); + + // Create request take instance object and add it to temporary map + long tid = getTid(event); + // TODO get proper take start time + long takeStartTime = timestamp - 5000; + long takeEndTime = timestamp; + Ros2TakeInstance takeInstance = new Ros2TakeInstance(serviceHandle, tid, request, sourcePubTimestamp, takeStartTime, takeEndTime); + fTakeInstances.put(serviceHandle, takeInstance); + + // Create request transport instance + Ros2MessageTransportInstance transportInstance = new Ros2MessageTransportInstance(clientHandle, serviceHandle, sourcePubTimestamp, takeStartTime); + addTransportInstance(ss, transportInstance); + } + + private void eventHandleResponseSend(@NonNull ITmfEvent event, ITmfStateSystemBuilder ss, long timestamp) { + Ros2ObjectHandle rmwServiceHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldRmwServiceHandle())); + HostProcessPointer response = hostProcessPointerFrom(event, (long) getField(event, LAYOUT.fieldResponse())); + long[] clientGidArray = (long[]) getField(event, LAYOUT.fieldClientGid()); + long sequenceNumber = (long) getField(event, LAYOUT.fieldSequenceNumber()); + long sourceTimestamp = (long) getField(event, LAYOUT.fieldTimestamp()); + + Ros2ObjectHandle serviceHandle = fServiceHandles.get(rmwServiceHandle); + if (null == serviceHandle) { + Activator.getInstance().logError("could not find service_handle for rmw_service_handle=" + rmwServiceHandle.toString()); //$NON-NLS-1$ + return; + } + + // Create response instance + Integer servicePubQuark = Ros2MessagesUtil.getServiceQuarkAndAdd(ss, fObjectsSs, timestamp, serviceHandle, ClientServiceInstanceType.SEND); + if (null == servicePubQuark) { + return; + } + HostThread thread = hostThreadFrom(event); + Ros2PubInstance responsePubInstance = new Ros2PubInstance(serviceHandle, thread.getTid(), response, timestamp); + // TODO figure out proper response publication start timestamp + long responsePubStartTime = timestamp - 5000; + long responsePubEndTime = timestamp; + ss.modifyAttribute(responsePubStartTime, responsePubInstance, servicePubQuark); + ss.modifyAttribute(responsePubEndTime, null, servicePubQuark); + + // Save for response send/take matching + Gid clientGid = new Gid(clientGidArray); + Ros2Response responseInfo = new Ros2Response(new Ros2Request(clientGid, sequenceNumber), sourceTimestamp); + fResponses.put(responseInfo, new Pair<>(serviceHandle, timestamp)); + } + + private void eventHandleResponseTake(@NonNull ITmfEvent event, ITmfStateSystemBuilder ss, long timestamp) { + Ros2ObjectHandle rmwClientHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldRmwClientHandle())); + HostProcessPointer response = hostProcessPointerFrom(event, (long) getField(event, LAYOUT.fieldResponse())); + long sequenceNumber = (long) getField(event, LAYOUT.fieldSequenceNumber()); + long sourceTimestamp = (long) getField(event, LAYOUT.fieldSourceTimestamp()); + + // Get client handle and GID + Ros2ObjectHandle clientHandle = fClientHandles.get(rmwClientHandle); + if (null == clientHandle) { + Activator.getInstance().logError("could not find client_handle for rmw_client_handle=" + rmwClientHandle.toString()); //$NON-NLS-1$ + return; + } + Ros2ClientObject clientObject = Ros2ObjectsUtil.getClientObjectFromHandle(fObjectsSs, timestamp, clientHandle); + if (null == clientObject) { + Activator.getInstance().logError("could not find client object for client handle=" + clientHandle.getHandle()); //$NON-NLS-1$ + return; + } + + // Create response take instance + long tid = getTid(event); + // TODO get proper start time + long takeStartTime = timestamp - 5000; + long takeEndTime = timestamp; + Ros2TakeInstance takeInstance = new Ros2TakeInstance(clientHandle, tid, response, sourceTimestamp, takeStartTime, takeEndTime); + Integer clientTakeQuark = Ros2MessagesUtil.getClientQuarkAndAdd(ss, fObjectsSs, timestamp, clientHandle, ClientServiceInstanceType.TAKE); + if (null == clientTakeQuark) { + return; + } + ss.modifyAttribute(takeInstance.getStartTime(), takeInstance, clientTakeQuark); + ss.modifyAttribute(takeInstance.getEndTime(), null, clientTakeQuark); + + /** + * Get response event. This response should only be received and + * considered by a single client (the client that made the request that + * this response is for). + */ + Ros2Request requestInfo = new Ros2Request(clientObject.getGid(), sequenceNumber); + Ros2Response responseInfo = new Ros2Response(requestInfo, sourceTimestamp); + Pair<@NonNull Ros2ObjectHandle, @NonNull Long> responseSourceInfo = fResponses.get(responseInfo); + if (null == responseSourceInfo) { + Activator.getInstance().logError("could not find corresponding response=" + responseInfo.toString()); //$NON-NLS-1$ + return; + } + Ros2ObjectHandle serviceHandle = responseSourceInfo.getFirst(); + long sourcePubTimestamp = responseSourceInfo.getSecond(); + + // Create response transport instance + Ros2MessageTransportInstance transportInstance = new Ros2MessageTransportInstance(serviceHandle, clientHandle, sourcePubTimestamp, takeStartTime); + addTransportInstance(ss, transportInstance); + } + private void createObjects(ITmfStateSystemBuilder ss) { /** * Get all node objects from the objects state system and create diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/messages/Ros2MessagesUtil.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/messages/Ros2MessagesUtil.java index b5621b891..b2064f621 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/messages/Ros2MessagesUtil.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/messages/Ros2MessagesUtil.java @@ -23,9 +23,11 @@ import org.eclipse.tracecompass.incubator.internal.ros2.core.analysis.objects.Ros2ObjectsUtil; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2CallbackPublicationInstance; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2MessageTransportInstance; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ClientObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2NodeObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ObjectHandle; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2PublisherObject; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ServiceObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2SubscriptionObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2TimerObject; import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem; @@ -50,6 +52,19 @@ public class Ros2MessagesUtil { public static final @NonNull String LIST_PUBLISHERS = "Publishers"; //$NON-NLS-1$ /** Attribute name for subscriptions list */ public static final @NonNull String LIST_SUBSCRIPTIONS = "Subscriptions"; //$NON-NLS-1$ + /** Attribute name for clients list */ + public static final @NonNull String LIST_CLIENTS = "Clients"; //$NON-NLS-1$ + /** Attribute name for services list */ + public static final @NonNull String LIST_SERVICES = "Services"; //$NON-NLS-1$ + + /** Types of instances for clients/services */ + public enum ClientServiceInstanceType { + /** Send instance */ + SEND, + /** Take or take+callback instance */ + TAKE; + } + /** Attribute name for timers list */ public static final @NonNull String LIST_TIMERS = "Timers"; //$NON-NLS-1$ /** Attribute name for transport instances list */ @@ -96,6 +111,20 @@ private static String[] getSubscriptionRelativeAttribute(@NonNull Ros2ObjectHand return new String[] { LIST_SUBSCRIPTIONS, subscriptionHandle.getStringId() }; } + /** + * Get client attribute relative to node attribute/quark. + */ + private static String[] getClientRelativeAttribute(@NonNull Ros2ObjectHandle clientHandle, @NonNull ClientServiceInstanceType type) { + return new String[] { LIST_CLIENTS, clientHandle.getStringId(), type.toString() }; + } + + /** + * Get service attribute relative to node attribute/quark. + */ + private static String[] getServiceRelativeAttribute(@NonNull Ros2ObjectHandle serviceHandle, @NonNull ClientServiceInstanceType type) { + return new String[] { LIST_SERVICES, serviceHandle.getStringId(), type.toString() }; + } + /** * Get timer attribute relative to node attribute/quark. */ @@ -225,6 +254,62 @@ public static int getNodeQuarkAndAdd(ITmfStateSystemBuilder ss, @NonNull Ros2Nod return ss.getQuarkRelativeAndAdd(nodeQuark, getSubscriptionRelativeAttribute(subscriptionHandle)); } + /** + * Get client quark and add if needed. + * + * @param ss + * the messages state system + * @param objectsSs + * the objects state system + * @param timestamp + * the timestamp within the client & node lifetime + * @param clientHandle + * the client handle + * @param type + * the instance type + * @return the client quark, or null + */ + public static @Nullable Integer getClientQuarkAndAdd(ITmfStateSystemBuilder ss, ITmfStateSystem objectsSs, long timestamp, @NonNull Ros2ObjectHandle clientHandle, @NonNull ClientServiceInstanceType type) { + Ros2ClientObject clientObject = Ros2ObjectsUtil.getClientObjectFromHandle(objectsSs, timestamp, clientHandle); + if (null == clientObject) { + Activator.getInstance().logError("could not find corresponding client object for clientHandle=" + clientHandle.toString()); //$NON-NLS-1$ + return null; + } + Integer nodeQuark = getNodeQuark(ss, objectsSs, timestamp, clientObject.getNodeHandle()); + if (null == nodeQuark) { + return null; + } + return ss.getQuarkRelativeAndAdd(nodeQuark, getClientRelativeAttribute(clientHandle, type)); + } + + /** + * Get service quark and add if needed. + * + * @param ss + * the messages state system + * @param objectsSs + * the objects state system + * @param timestamp + * the timestamp within the service & node lifetime + * @param serviceHandle + * the service handle + * @param type + * the instance type + * @return the service quark, or null + */ + public static @Nullable Integer getServiceQuarkAndAdd(ITmfStateSystemBuilder ss, ITmfStateSystem objectsSs, long timestamp, @NonNull Ros2ObjectHandle serviceHandle, @NonNull ClientServiceInstanceType type) { + Ros2ServiceObject serviceObject = Ros2ObjectsUtil.getServiceObjectFromHandle(objectsSs, timestamp, serviceHandle); + if (null == serviceObject) { + Activator.getInstance().logError("could not find corresponding client object for serviceHandle=" + serviceHandle.toString()); //$NON-NLS-1$ + return null; + } + Integer nodeQuark = getNodeQuark(ss, objectsSs, timestamp, serviceObject.getNodeHandle()); + if (null == nodeQuark) { + return null; + } + return ss.getQuarkRelativeAndAdd(nodeQuark, getServiceRelativeAttribute(serviceHandle, type)); + } + /** * Get timer quark and add if needed. * @@ -345,6 +430,72 @@ public static int getCallbackPublicationInstanceQuarkAndAdd(ITmfStateSystemBuild return null; } + /** + * Get client quark. + * + * @param ss + * the messages state system + * @param objectsSs + * the objects state system + * @param timestamp + * the timestamp within the client & node lifetime + * @param clientHandle + * the client handle + * @param type + * the instance type + * @return the client quark, or null + */ + public static @Nullable Integer getClientQuark(ITmfStateSystem ss, ITmfStateSystem objectsSs, long timestamp, @NonNull Ros2ObjectHandle clientHandle, @NonNull ClientServiceInstanceType type) { + Ros2ClientObject clientObject = Ros2ObjectsUtil.getClientObjectFromHandle(objectsSs, timestamp, clientHandle); + if (null == clientObject) { + Activator.getInstance().logError("could not find corresponding client object for clientHandle=" + clientHandle.toString()); //$NON-NLS-1$ + return null; + } + Integer nodeQuark = getNodeQuark(ss, objectsSs, timestamp, clientObject.getNodeHandle()); + if (null == nodeQuark) { + return null; + } + try { + return ss.getQuarkRelative(nodeQuark, getClientRelativeAttribute(clientHandle, type)); + } catch (AttributeNotFoundException e) { + // Do nothing + } + return null; + } + + /** + * Get service quark. + * + * @param ss + * the messages state system + * @param objectsSs + * the objects state system + * @param timestamp + * the timestamp within the service & node lifetime + * @param serviceHandle + * the service handle + * @param type + * the instance type + * @return the service quark, or null + */ + public static @Nullable Integer getServiceQuark(ITmfStateSystem ss, ITmfStateSystem objectsSs, long timestamp, @NonNull Ros2ObjectHandle serviceHandle, @NonNull ClientServiceInstanceType type) { + Ros2ServiceObject serviceObject = Ros2ObjectsUtil.getServiceObjectFromHandle(objectsSs, timestamp, serviceHandle); + if (null == serviceObject) { + Activator.getInstance().logError("could not find corresponding service object for serviceHandle=" + serviceHandle.toString()); //$NON-NLS-1$ + return null; + } + Integer nodeQuark = getNodeQuark(ss, objectsSs, timestamp, serviceObject.getNodeHandle()); + if (null == nodeQuark) { + return null; + } + try { + return ss.getQuarkRelative(nodeQuark, getServiceRelativeAttribute(serviceHandle, type)); + } catch (AttributeNotFoundException e) { + // Do nothing + } + return null; + } + /** * Get timer quark. * diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/objects/Ros2ObjectsStateProvider.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/objects/Ros2ObjectsStateProvider.java index 3a9d09120..ee22e3656 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/objects/Ros2ObjectsStateProvider.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/objects/Ros2ObjectsStateProvider.java @@ -11,9 +11,7 @@ package org.eclipse.tracecompass.incubator.internal.ros2.core.analysis.objects; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Map; import java.util.Objects; @@ -26,9 +24,11 @@ import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Gid; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2CallbackObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2CallbackType; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ClientObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2NodeObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ObjectHandle; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2PublisherObject; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ServiceObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2SubscriptionObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2TimerObject; import org.eclipse.tracecompass.statesystem.core.ITmfStateSystemBuilder; @@ -55,13 +55,16 @@ public class Ros2ObjectsStateProvider extends AbstractRos2StateProvider { private Map<@NonNull Gid, ITmfEvent> fDdsCreateWriterEvents = Maps.newHashMap(); private Map fRmwPublisherInitEvents = Maps.newHashMap(); // Callbacks - private Collection fIgnoredCallbacks = new ArrayList<>(); private Map> fCallbackOwners = Maps.newHashMap(); // Subscriptions private Map<@NonNull Gid, ITmfEvent> fDdsCreateReaderEvents = Maps.newHashMap(); private Map<@NonNull Ros2ObjectHandle, ITmfEvent> fRmwSubscriptionInitEvents = Maps.newHashMap(); private Map<@NonNull Ros2ObjectHandle, ITmfEvent> fRclSubscriptionInitEvents = Maps.newHashMap(); private Map fRclcppSubscriptionInitEvents = Maps.newHashMap(); + // Services + private Map<@NonNull Ros2ObjectHandle, ITmfEvent> fRclServiceInitEvents = Maps.newHashMap(); + // Clients + private Map<@NonNull Ros2ObjectHandle, ITmfEvent> fRmwClientInitEvents = Maps.newHashMap(); // Timers private Map<@NonNull Ros2ObjectHandle, ITmfEvent> fRclTimerInit = Maps.newHashMap(); private Map<@NonNull Ros2ObjectHandle, ITmfEvent> fRclcppTimerCallbackAdded = Maps.newHashMap(); @@ -99,7 +102,8 @@ protected void eventHandle(@NonNull ITmfEvent event) { eventHandlePublisher(event, ss); eventHandleSubscription(event, ss); eventHandleTimer(event, ss, timestamp); - eventHandleService(event); + eventHandleService(event, ss, timestamp); + eventHandleClient(event, ss, timestamp); eventHandleCallback(event, ss, timestamp); } @@ -376,14 +380,79 @@ private void eventHandleTimerLinkNode(@NonNull ITmfEvent event, ITmfStateSystemB } } - private void eventHandleService(@NonNull ITmfEvent event) { + private void eventHandleService(@NonNull ITmfEvent event, ITmfStateSystemBuilder ss, long timestamp) { + eventHandleServiceInit(event); + eventHandleServiceCallbackAdded(event, ss, timestamp); + } + + private void eventHandleServiceInit(@NonNull ITmfEvent event) { + // rcl_service_init + if (isEvent(event, LAYOUT.eventRclServiceInit())) { + Ros2ObjectHandle serviceHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldServiceHandle())); + + // Add to temporary map + fRclServiceInitEvents.put(serviceHandle, event); + } + } + + private void eventHandleServiceCallbackAdded(@NonNull ITmfEvent event, ITmfStateSystemBuilder ss, long timestamp) { // rclcpp_service_callback_added if (isEvent(event, LAYOUT.eventRclcppServiceCallbackAdded())) { + Ros2ObjectHandle serviceHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldServiceHandle())); HostProcessPointer callback = hostProcessPointerFrom(event, (long) getField(event, LAYOUT.fieldCallback())); - // Add to list of ignored callbacks, since we don't currently - // process services - fIgnoredCallbacks.add(callback); + // Add callback owner info to map + fCallbackOwners.put(callback, new Pair<>(Ros2CallbackType.SERVICE, serviceHandle)); + + // Get correspondingrcl_service_init event + ITmfEvent rclServiceInit = fRclServiceInitEvents.remove(serviceHandle); + if (null == rclServiceInit) { + Activator.getInstance().logError("could not find corresponding rcl_service_init event for serviceHandle=" + serviceHandle.toString()); //$NON-NLS-1$ + return; + } + Ros2ObjectHandle nodeHandle = handleFrom(event, (long) getField(rclServiceInit, LAYOUT.fieldNodeHandle())); + Ros2ObjectHandle rmwServiceHandle = handleFrom(event, (long) getField(rclServiceInit, LAYOUT.fieldRmwServiceHandle())); + String serviceName = Objects.requireNonNull((String) getField(rclServiceInit, LAYOUT.fieldServiceName())); + + // Add to services list + Ros2ServiceObject serviceObject = new Ros2ServiceObject(serviceHandle, rmwServiceHandle, serviceName, nodeHandle, callback); + int serviceQuark = Ros2ObjectsUtil.getServiceQuarkAndAdd(ss, serviceObject.getHandle()); + ss.modifyAttribute(timestamp, serviceObject, serviceQuark); + } + } + + private void eventHandleClient(@NonNull ITmfEvent event, ITmfStateSystemBuilder ss, long timestamp) { + // rmw_client_init + if (isEvent(event, LAYOUT.eventRmwClientInit())) { + Ros2ObjectHandle rmwClientHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldRmwClientHandle())); + + // Add to temporary map + fRmwClientInitEvents.put(rmwClientHandle, event); + + return; + } + + // rcl_client_init + if (isEvent(event, LAYOUT.eventRclClientInit())) { + Ros2ObjectHandle clientHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldClientHandle())); + Ros2ObjectHandle nodeHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldNodeHandle())); + Ros2ObjectHandle rmwClientHandle = handleFrom(event, (long) getField(event, LAYOUT.fieldRmwClientHandle())); + String serviceName = Objects.requireNonNull((String) getField(event, LAYOUT.fieldServiceName())); + + // Get corresponding rmw_client_init event + ITmfEvent rmwClientInit = fRmwClientInitEvents.remove(rmwClientHandle); + if (null == rmwClientInit) { + Activator.getInstance().logError("could not find corresponding rmw_client_init event for rmwClientHandle=" + rmwClientHandle.toString()); //$NON-NLS-1$ + return; + } + long[] gidArray = (long[]) getField(rmwClientInit, LAYOUT.fieldGid()); + Gid gid = new Gid(gidArray); + + // Add to clients list + Ros2ClientObject clientObject = new Ros2ClientObject(clientHandle, rmwClientHandle, serviceName, nodeHandle, gid); + int clientQuark = Ros2ObjectsUtil.getClientQuarkAndAdd(ss, clientHandle); + ss.modifyAttribute(timestamp, clientObject, clientQuark); + return; } } @@ -393,11 +462,6 @@ private void eventHandleCallback(@NonNull ITmfEvent event, ITmfStateSystemBuilde HostProcessPointer callback = hostProcessPointerFrom(event, (long) getField(event, LAYOUT.fieldCallback())); String symbol = (String) getField(event, LAYOUT.fieldSymbol()); - // Check if we should ignore this callback registration - if (fIgnoredCallbacks.contains(callback)) { - return; - } - // Get owner info from map Pair ownerInfo = fCallbackOwners.remove(callback); if (null == ownerInfo) { diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/objects/Ros2ObjectsUtil.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/objects/Ros2ObjectsUtil.java index b6efdc70e..3bfc91db4 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/objects/Ros2ObjectsUtil.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/objects/Ros2ObjectsUtil.java @@ -24,10 +24,12 @@ import org.eclipse.tracecompass.incubator.internal.ros2.core.model.HostProcessPointer; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2CallbackObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2CallbackType; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ClientObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2NodeObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2Object; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ObjectHandle; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2PublisherObject; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ServiceObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2SubscriptionObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2TimerObject; import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem; @@ -50,6 +52,8 @@ public class Ros2ObjectsUtil { private static final @NonNull String OBJECT_NODE = "Nodes"; //$NON-NLS-1$ private static final @NonNull String OBJECT_PUBLISHER = "Publishers"; //$NON-NLS-1$ private static final @NonNull String OBJECT_SUBSCRIPTION = "Subscriptions"; //$NON-NLS-1$ + private static final @NonNull String OBJECT_CLIENT = "Clients"; //$NON-NLS-1$ + private static final @NonNull String OBJECT_SERVICE = "Services"; //$NON-NLS-1$ private static final @NonNull String OBJECT_TIMER = "Timers"; //$NON-NLS-1$ private static final @NonNull String OBJECT_CALLBACK = "Callbacks"; //$NON-NLS-1$ @@ -75,6 +79,14 @@ private static String[] getSubscriptionAttribute(@NonNull String stringId) { return new String[] { OBJECT_SUBSCRIPTION, stringId }; } + private static String[] getClientAttribute(@NonNull String stringId) { + return new String[] { OBJECT_CLIENT, stringId }; + } + + private static String[] getServiceAttribute(@NonNull String stringId) { + return new String[] { OBJECT_SERVICE, stringId }; + } + private static String[] getTimerAttribute(@NonNull String stringId) { return new String[] { OBJECT_TIMER, stringId }; } @@ -95,6 +107,14 @@ private static String[] getSubscriptionAttribute(@NonNull Ros2ObjectHandle subsc return getSubscriptionAttribute(subscriptionHandle.getStringId()); } + private static String[] getClientAttribute(@NonNull Ros2ObjectHandle clientHandle) { + return getClientAttribute(clientHandle.getStringId()); + } + + private static String[] getServiceAttribute(@NonNull Ros2ObjectHandle serviceHandle) { + return getServiceAttribute(serviceHandle.getStringId()); + } + private static String[] getTimerAttribute(@NonNull Ros2ObjectHandle timerHandle) { return getTimerAttribute(timerHandle.getStringId()); } @@ -145,6 +165,34 @@ public static int getSubscriptionQuarkAndAdd(ITmfStateSystemBuilder ss, @NonNull return ss.getQuarkAbsoluteAndAdd(getSubscriptionAttribute(subscriptionHandle)); } + /** + * Get client quark and add if needed. + * + * @param ss + * the objects state system + * @param clientHandle + * the client handle + * @return the client quark + */ + public static int getClientQuarkAndAdd(ITmfStateSystemBuilder ss, @NonNull Ros2ObjectHandle clientHandle) { + assertStateSystem(ss); + return ss.getQuarkAbsoluteAndAdd(getClientAttribute(clientHandle)); + } + + /** + * Get client quark and add if needed. + * + * @param ss + * the objects state system + * @param serviceHandle + * the service handle + * @return the service quark + */ + public static int getServiceQuarkAndAdd(ITmfStateSystemBuilder ss, @NonNull Ros2ObjectHandle serviceHandle) { + assertStateSystem(ss); + return ss.getQuarkAbsoluteAndAdd(getServiceAttribute(serviceHandle)); + } + /** * Get timer quark and add if needed. * @@ -200,6 +248,24 @@ private static Integer getSubscriptionQuark(ITmfStateSystem ss, @NonNull Ros2Obj } } + private static Integer getClientQuark(ITmfStateSystem ss, @NonNull Ros2ObjectHandle clientHandle) { + assertStateSystem(ss); + try { + return ss.getQuarkAbsolute(getClientAttribute(clientHandle)); + } catch (AttributeNotFoundException e) { + return null; + } + } + + private static Integer getServiceQuark(ITmfStateSystem ss, @NonNull Ros2ObjectHandle serviceHandle) { + assertStateSystem(ss); + try { + return ss.getQuarkAbsolute(getServiceAttribute(serviceHandle)); + } catch (AttributeNotFoundException e) { + return null; + } + } + private static Integer getTimerQuark(ITmfStateSystem ss, @NonNull Ros2ObjectHandle timerHandle) { assertStateSystem(ss); try { @@ -348,6 +414,54 @@ private static Integer getCallbackQuark(ITmfStateSystem ss, @NonNull HostProcess } } + /** + * Get client object from client handle. + * + * @param ss + * the objects state system + * @param timestamp + * the timestamp + * @param clientHandle + * the client handle + * @return the client object, or null if not found + */ + public static @Nullable Ros2ClientObject getClientObjectFromHandle(ITmfStateSystem ss, long timestamp, @NonNull Ros2ObjectHandle clientHandle) { + Integer clientQuark = getClientQuark(ss, clientHandle); + if (null == clientQuark) { + return null; + } + + try { + return (Ros2ClientObject) ss.querySingleState(timestamp, clientQuark).getValue(); + } catch (StateSystemDisposedException e) { + return null; + } + } + + /** + * Get service object from service handle. + * + * @param ss + * the objects state system + * @param timestamp + * the timestamp + * @param serviceHandle + * the service handle + * @return the service object, or null if not found + */ + public static @Nullable Ros2ServiceObject getServiceObjectFromHandle(ITmfStateSystem ss, long timestamp, @NonNull Ros2ObjectHandle serviceHandle) { + Integer serviceQuark = getServiceQuark(ss, serviceHandle); + if (null == serviceQuark) { + return null; + } + + try { + return (Ros2ServiceObject) ss.querySingleState(timestamp, serviceQuark).getValue(); + } catch (StateSystemDisposedException e) { + return null; + } + } + /** * Get timer object from timer handle. * @@ -414,6 +528,34 @@ private static Integer getCallbackQuark(ITmfStateSystem ss, @NonNull HostProcess return getObjectFromHandle(ss, Ros2NodeObject.class, OBJECT_NODE, nodeHandle); } + /** + * Get client object from client handle. + * + * @param ss + * the objects state system + * @param clientHandle + * the client handle + * @return the client object, or null if not found + * @see Ros2ObjectsUtil#getObjectFromHandle + */ + public static @Nullable Ros2ClientObject getClientObjectFromHandle(ITmfStateSystem ss, @NonNull Ros2ObjectHandle clientHandle) { + return getObjectFromHandle(ss, Ros2ClientObject.class, OBJECT_CLIENT, clientHandle); + } + + /** + * Get service object from service handle. + * + * @param ss + * the objects state system + * @param serviceHandle + * the service handle + * @return the service object, or null if not found + * @see Ros2ObjectsUtil#getObjectFromHandle + */ + public static @Nullable Ros2ServiceObject getServiceObjectFromHandle(ITmfStateSystem ss, @NonNull Ros2ObjectHandle serviceHandle) { + return getObjectFromHandle(ss, Ros2ServiceObject.class, OBJECT_SERVICE, serviceHandle); + } + /** * Get timer object from timer handle. * @@ -592,6 +734,28 @@ private static Integer getCallbackQuark(ITmfStateSystem ss, @NonNull HostProcess return getObjects(ss, Ros2SubscriptionObject.class, OBJECT_SUBSCRIPTION); } + /** + * Get all clients. + * + * @param ss + * the objects state system + * @return the client objects + */ + public static Collection<@NonNull Ros2ClientObject> getClientObjects(ITmfStateSystem ss) { + return getObjects(ss, Ros2ClientObject.class, OBJECT_CLIENT); + } + + /** + * Get all services. + * + * @param ss + * the objects state system + * @return the service objects + */ + public static Collection<@NonNull Ros2ServiceObject> getServiceObjects(ITmfStateSystem ss) { + return getObjects(ss, Ros2ServiceObject.class, OBJECT_SERVICE); + } + /** * Get all timers. * diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/messages/Ros2MessageTransportInstance.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/messages/Ros2MessageTransportInstance.java index e2d77986a..12003f149 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/messages/Ros2MessageTransportInstance.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/messages/Ros2MessageTransportInstance.java @@ -48,7 +48,7 @@ public class Ros2MessageTransportInstance extends CustomStateValue { * @param destinationSubscriptionHandle * the destination subscription handle * @param sourcePublicationTimestamp - * the source publication timestmap + * the source publication timestamp * @param destinationTakeTimestamp * the destination take timestamp */ diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/messages/Ros2Request.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/messages/Ros2Request.java new file mode 100644 index 000000000..b99604966 --- /dev/null +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/messages/Ros2Request.java @@ -0,0 +1,83 @@ +/********************************************************************** + * Copyright (c) 2024 Apex.AI, Inc. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +package org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Gid; + +import com.google.common.base.Objects; + +/** + * Container for ROS 2 request info. + * + * This is used to match a request sent by a client to a request taken by a + * service. + * + * @author Christophe Bedard + */ +public class Ros2Request { + + private final @NonNull Gid fClientGid; + private final long fSequenceNumber; + + /** + * Constructor + * + * @param clientGid + * the GID of the client sending the request + * @param sequenceNumber + * the request sequence number + */ + public Ros2Request(@NonNull Gid clientGid, long sequenceNumber) { + fClientGid = clientGid; + fSequenceNumber = sequenceNumber; + } + + /** + * @return the GID of the client sending the request + */ + public @NonNull Gid getClientGid() { + return fClientGid; + } + + /** + * @return the request sequence number + */ + public long getSequenceNumber() { + return fSequenceNumber; + } + + @Override + public int hashCode() { + return Objects.hashCode(fClientGid, fSequenceNumber); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Ros2Request o = (Ros2Request) obj; + return o.fClientGid.equals(fClientGid) && o.fSequenceNumber == fSequenceNumber; + } + + @Override + public @NonNull String toString() { + return String.format("Ros2Request: clientGid=%d, sequenceNumber=%s", fClientGid.toString(), fSequenceNumber); //$NON-NLS-1$ + } +} diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/messages/Ros2Response.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/messages/Ros2Response.java new file mode 100644 index 000000000..34b6e4b4e --- /dev/null +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/messages/Ros2Response.java @@ -0,0 +1,82 @@ +/********************************************************************** + * Copyright (c) 2024 Apex.AI, Inc. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +package org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.common.base.Objects; + +/** + * Container for ROS 2 response info. + * + * This is used to match a response sent by a service to a response taken by a + * client. + * + * @author Christophe Bedard + */ +public class Ros2Response { + + private final @NonNull Ros2Request fRequestInfo; + private final long fSourceTimestamp; + + /** + * Constructor + * + * @param requestInfo + * the info of the request that this response is for + * @param sourceTimestamp + * the source timestamp of the response + */ + public Ros2Response(@NonNull Ros2Request requestInfo, long sourceTimestamp) { + fRequestInfo = requestInfo; + fSourceTimestamp = sourceTimestamp; + } + + /** + * @return the info of the request that this response is for + */ + public @NonNull Ros2Request getRequestInfo() { + return fRequestInfo; + } + + /** + * @return the source timestamp of the response + */ + public long getSourceTimestamp() { + return fSourceTimestamp; + } + + @Override + public int hashCode() { + return Objects.hashCode(fRequestInfo, fSourceTimestamp); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Ros2Response o = (Ros2Response) obj; + return o.fRequestInfo.equals(fRequestInfo) && o.fSourceTimestamp == fSourceTimestamp; + } + + @Override + public @NonNull String toString() { + return String.format("Ros2Response: requestInfo=[%s], sourceTimestamp=%s", fRequestInfo.toString(), fSourceTimestamp); //$NON-NLS-1$ + } +} diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/objects/Ros2CallbackType.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/objects/Ros2CallbackType.java index d63e7a01d..277a93c6e 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/objects/Ros2CallbackType.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/objects/Ros2CallbackType.java @@ -24,6 +24,9 @@ public enum Ros2CallbackType { /** * Timer */ - TIMER; - // TODO service, etc. + TIMER, + /** + * Service (i.e., service server) + */ + SERVICE; } diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/objects/Ros2ClientObject.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/objects/Ros2ClientObject.java new file mode 100644 index 000000000..71a70c5ed --- /dev/null +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/objects/Ros2ClientObject.java @@ -0,0 +1,80 @@ +/********************************************************************** + * Copyright (c) 2024 Apex.AI, Inc. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +package org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.tracecompass.datastore.core.serialization.ISafeByteBufferReader; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.HostInfo; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.HostProcess; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.HostProcessPointer; + +/** + * Container for ROS 2 client object (i.e., service client). + * + * @author Christophe Bedard + */ +public class Ros2ClientObject extends Ros2PubSubObject { + + /** Custom type value ID for this object */ + public static final byte CUSTOM_TYPE_ID = 75; + /** CustomStateValueFactory for this object */ + @SuppressWarnings("restriction") + public static final @NonNull CustomStateValueFactory ROS2_CLIENT_OBJECT_VALUE_FACTORY = b -> Ros2ClientObject.read(b); + + // We do not currently collect a DDS pointer for clients + private static final @NonNull HostProcessPointer HOST_PROCESS_POINTER_UNUSED = new HostProcessPointer(new HostProcess(new HostInfo(StringUtils.EMPTY, StringUtils.EMPTY), 0L), 0L); + + /** + * Constructor + * + * @param clientHandle + * the client handle + * @param rmwClientHandle + * the rmw client handle + * @param nodeHandle + * the node handle + * @param serviceName + * the service name + * @param gid + * the rmw GID + */ + public Ros2ClientObject(@NonNull Ros2ObjectHandle clientHandle, @NonNull Ros2ObjectHandle rmwClientHandle, @NonNull String serviceName, @NonNull Ros2ObjectHandle nodeHandle, @NonNull Gid gid) { + super(clientHandle, rmwClientHandle, serviceName, nodeHandle, gid, HOST_PROCESS_POINTER_UNUSED); + } + + @Override + public String toString() { + return String.format("Ros2ClientObject: %s", super.toString()); //$NON-NLS-1$ + } + + @Override + protected @NonNull Byte getCustomTypeId() { + return CUSTOM_TYPE_ID; + } + + /** + * @param buffer + * the buffer + * @return the value + */ + public static @NonNull Ros2ClientObject read(ISafeByteBufferReader buffer) { + Ros2ObjectHandle clientHandle = Ros2ObjectHandle.read(buffer); + Ros2ObjectHandle rmwClientHandle = Ros2ObjectHandle.read(buffer); + String serviceName = buffer.getString(); + Ros2ObjectHandle nodeHandle = Ros2ObjectHandle.read(buffer); + Gid gid = Gid.read(buffer); + // It's unused, but make sure to read the whole buffer + HostProcessPointer.read(buffer); + return new Ros2ClientObject(clientHandle, rmwClientHandle, serviceName, nodeHandle, gid); + } +} diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/objects/Ros2ServiceObject.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/objects/Ros2ServiceObject.java new file mode 100644 index 000000000..9ed8848b3 --- /dev/null +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/model/objects/Ros2ServiceObject.java @@ -0,0 +1,103 @@ +/********************************************************************** + * Copyright (c) 2024 Apex.AI, Inc. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +package org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.tracecompass.datastore.core.serialization.ISafeByteBufferReader; +import org.eclipse.tracecompass.datastore.core.serialization.ISafeByteBufferWriter; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.HostInfo; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.HostProcess; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.HostProcessPointer; + +/** + * Container for ROS 2 service object (i.e., service server). + * + * @author Christophe Bedard + */ +public class Ros2ServiceObject extends Ros2PubSubObject { + + /** Custom type value ID for this object */ + public static final byte CUSTOM_TYPE_ID = 76; + /** CustomStateValueFactory for this object */ + @SuppressWarnings("restriction") + public static final @NonNull CustomStateValueFactory ROS2_SERVICE_OBJECT_VALUE_FACTORY = b -> Ros2ServiceObject.read(b); + + // We do not currently collect a DDS pointer for services + private static final @NonNull HostProcessPointer HOST_PROCESS_POINTER_UNUSED = new HostProcessPointer(new HostProcess(new HostInfo(StringUtils.EMPTY, StringUtils.EMPTY), 0L), 0L); + + private final @NonNull HostProcessPointer fCallback; + private final int fSerializedValueSize; + + /** + * Constructor + * + * @param serviceHandle + * the service handle + * @param rmwServiceHandle + * the rmw service handle + * @param nodeHandle + * the node handle + * @param serviceName + * the service name + * @param callback + * the corresponding callback object + */ + public Ros2ServiceObject(@NonNull Ros2ObjectHandle serviceHandle, @NonNull Ros2ObjectHandle rmwServiceHandle, @NonNull String serviceName, @NonNull Ros2ObjectHandle nodeHandle, @NonNull HostProcessPointer callback) { + super(serviceHandle, rmwServiceHandle, serviceName, nodeHandle, new Gid(ArrayUtils.EMPTY_LONG_ARRAY), HOST_PROCESS_POINTER_UNUSED); + fCallback = callback; + + int size = 0; + size += super.getSerializedValueSize(); + size += fCallback.getSerializedValueSize(); + fSerializedValueSize = size; + } + + @Override + public String toString() { + return String.format("Ros2ServiceObject: %s", super.toString()); //$NON-NLS-1$ + } + + @Override + protected @NonNull Byte getCustomTypeId() { + return CUSTOM_TYPE_ID; + } + + @Override + protected void serializeValue(@NonNull ISafeByteBufferWriter buffer) { + super.serializeValue(buffer); + fCallback.serializeValue(buffer); + } + + @Override + protected int getSerializedValueSize() { + return fSerializedValueSize; + } + + /** + * @param buffer + * the buffer + * @return the value + */ + public static @NonNull Ros2ServiceObject read(ISafeByteBufferReader buffer) { + Ros2ObjectHandle serviceHandle = Ros2ObjectHandle.read(buffer); + Ros2ObjectHandle rmwServiceHandle = Ros2ObjectHandle.read(buffer); + String serviceName = buffer.getString(); + Ros2ObjectHandle nodeHandle = Ros2ObjectHandle.read(buffer); + // GID and HostProcessPointer are unused, but make sure to read the whole buffer + Gid.read(buffer); + HostProcessPointer.read(buffer); + HostProcessPointer callback = HostProcessPointer.read(buffer); + return new Ros2ServiceObject(serviceHandle, rmwServiceHandle, serviceName, nodeHandle, callback); + } +} diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/trace/layout/IRos2EventLayout.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/trace/layout/IRos2EventLayout.java index b7207c63b..a12f6adbe 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/trace/layout/IRos2EventLayout.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/trace/layout/IRos2EventLayout.java @@ -156,11 +156,36 @@ public default Collection getEventNames() { */ String eventRclcppServiceCallbackAdded(); + /** + * rmw_take_request + */ + String eventRmwTakeRequest(); + + /** + * rmw_send_response + */ + String eventRmwSendResponse(); + + /** + * rmw_client_init + */ + String eventRmwClientInit(); + /** * rcl_client_init */ String eventRclClientInit(); + /** + * rmw_send_request + */ + String eventRmwSendRequest(); + + /** + * rmw_take_response + */ + String eventRmwTakeResponse(); + /** * rcl_timer_init */ @@ -282,6 +307,10 @@ public default Collection getEventNames() { String fieldServiceName(); String fieldClientHandle(); String fieldRmwClientHandle(); + String fieldRequest(); + String fieldClientGid(); + String fieldSequenceNumber(); + String fieldResponse(); String fieldTimerHandle(); String fieldPeriod(); String fieldSymbol(); diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/trace/layout/Ros2RollingEventLayout.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/trace/layout/Ros2RollingEventLayout.java index 06423f163..be80d1274 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/trace/layout/Ros2RollingEventLayout.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/trace/layout/Ros2RollingEventLayout.java @@ -39,7 +39,12 @@ public class Ros2RollingEventLayout implements IRos2EventLayout { private static final String RCLCPP_TAKE = "rclcpp_take"; private static final String RCL_SERVICE_INIT = "rcl_service_init"; private static final String RCLCPP_SERVICE_CALLBACK_ADDED = "rclcpp_service_callback_added"; + private static final String RMW_TAKE_REQUEST = "rmw_take_request"; + private static final String RMW_SEND_RESPONSE = "rmw_send_response"; + private static final String RMW_CLIENT_INIT = "rmw_client_init"; private static final String RCL_CLIENT_INIT = "rcl_client_init"; + private static final String RMW_SEND_REQUEST = "rmw_send_request"; + private static final String RMW_TAKE_RESPONSE = "rmw_take_response"; private static final String RCL_TIMER_INIT = "rcl_timer_init"; private static final String RCLCPP_TIMER_CALLBACK_ADDED = "rclcpp_timer_callback_added"; private static final String RCLCPP_TIMER_LINK_NODE = "rclcpp_timer_link_node"; @@ -85,6 +90,10 @@ public class Ros2RollingEventLayout implements IRos2EventLayout { private static final String SERVICE_NAME = "service_name"; private static final String CLIENT_HANDLE = "client_handle"; private static final String RMW_CLIENT_HANDLE = "rmw_client_handle"; + private static final String CLIENT_GID = "client_gid"; + private static final String REQUEST = "request"; + private static final String SEQUENCE_NUMBER = "sequence_number"; + private static final String RESPONSE = "response"; private static final String TIMER_HANDLE = "timer_handle"; private static final String PERIOD = "period"; private static final String SYMBOL = "symbol"; @@ -246,6 +255,27 @@ public String eventRclcppServiceCallbackAdded() { return getProviderName() + RCLCPP_SERVICE_CALLBACK_ADDED; } + // rmw_take_request + + @Override + public String eventRmwTakeRequest() { + return getProviderName() + RMW_TAKE_REQUEST; + } + + // rmw_send_response + + @Override + public String eventRmwSendResponse() { + return getProviderName() + RMW_SEND_RESPONSE; + } + + // rmw_client_init + + @Override + public String eventRmwClientInit() { + return getProviderName() + RMW_CLIENT_INIT; + } + // rcl_client_init @Override @@ -253,6 +283,20 @@ public String eventRclClientInit() { return getProviderName() + RCL_CLIENT_INIT; } + // rmw_send_request + + @Override + public String eventRmwSendRequest() { + return getProviderName() + RMW_SEND_REQUEST; + } + + // rmw_take_response + + @Override + public String eventRmwTakeResponse() { + return getProviderName() + RMW_TAKE_RESPONSE; + } + // rcl_timer_init @Override @@ -502,6 +546,26 @@ public String fieldRmwClientHandle() { return RMW_CLIENT_HANDLE; } + @Override + public String fieldRequest() { + return REQUEST; + } + + @Override + public String fieldClientGid() { + return CLIENT_GID; + } + + @Override + public String fieldSequenceNumber() { + return SEQUENCE_NUMBER; + } + + @Override + public String fieldResponse() { + return RESPONSE; + } + @Override public String fieldTimerHandle() { return TIMER_HANDLE; diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.ui/src/org/eclipse/tracecompass/incubator/internal/ros2/ui/views/Ros2ObjectTreeLabelProvider.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.ui/src/org/eclipse/tracecompass/incubator/internal/ros2/ui/views/Ros2ObjectTreeLabelProvider.java index 69566fb08..459c742d2 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.ui/src/org/eclipse/tracecompass/incubator/internal/ros2/ui/views/Ros2ObjectTreeLabelProvider.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.ui/src/org/eclipse/tracecompass/incubator/internal/ros2/ui/views/Ros2ObjectTreeLabelProvider.java @@ -37,6 +37,8 @@ public class Ros2ObjectTreeLabelProvider { private static final String COLUMN_TEXT_PREFIX_NODE = "🔲 "; //$NON-NLS-1$ private static final String COLUMN_TEXT_PREFIX_PUBLISHER = "↗️ "; //$NON-NLS-1$ private static final String COLUMN_TEXT_PREFIX_SUBSCRIPTION = "↘️ "; //$NON-NLS-1$ + private static final String COLUMN_TEXT_PREFIX_CLIENT = "S↗️ "; //$NON-NLS-1$ + private static final String COLUMN_TEXT_PREFIX_SERVICE = "S↘️ "; //$NON-NLS-1$ private static final String COLUMN_TEXT_PREFIX_TIMER = "⏳ "; //$NON-NLS-1$ private Ros2ObjectTreeLabelProvider() { @@ -97,6 +99,10 @@ public static String getColumnText(Object element, int columnIndex) { return COLUMN_TEXT_PREFIX_PUBLISHER + entry.getName(); } else if (Ros2ObjectTimeGraphEntryModelType.SUBSCRIPTION == type) { return COLUMN_TEXT_PREFIX_SUBSCRIPTION + entry.getName(); + } else if (Ros2ObjectTimeGraphEntryModelType.CLIENT == type) { + return COLUMN_TEXT_PREFIX_CLIENT + entry.getName(); + } else if (Ros2ObjectTimeGraphEntryModelType.SERVICE == type) { + return COLUMN_TEXT_PREFIX_SERVICE + entry.getName(); } else if (Ros2ObjectTimeGraphEntryModelType.TIMER == type) { return COLUMN_TEXT_PREFIX_TIMER + entry.getName(); } diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros2.ui/src/org/eclipse/tracecompass/incubator/internal/ros2/ui/views/messages/Ros2MessagesPresentationProvider.java b/tracetypes/org.eclipse.tracecompass.incubator.ros2.ui/src/org/eclipse/tracecompass/incubator/internal/ros2/ui/views/messages/Ros2MessagesPresentationProvider.java index c69ab9adc..82a6f3772 100644 --- a/tracetypes/org.eclipse.tracecompass.incubator.ros2.ui/src/org/eclipse/tracecompass/incubator/internal/ros2/ui/views/messages/Ros2MessagesPresentationProvider.java +++ b/tracetypes/org.eclipse.tracecompass.incubator.ros2.ui/src/org/eclipse/tracecompass/incubator/internal/ros2/ui/views/messages/Ros2MessagesPresentationProvider.java @@ -29,8 +29,10 @@ import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2CallbackInstance; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2PubInstance; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2TakeInstance; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ClientObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2NodeObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2PublisherObject; +import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ServiceObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2SubscriptionObject; import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2TimerObject; import org.eclipse.tracecompass.tmf.core.model.StyleProperties; @@ -166,6 +168,18 @@ private static int getObjectEntryModelIndex(ITimeEvent event, ITmfTreeDataModel Ros2SubscriptionObject subscriptionObject = (Ros2SubscriptionObject) messagesModel.getObject(); hash = fHasher.hashUnencodedChars(subscriptionObject.getTopicName()).asLong(); + Iterator<@NonNull Object> stateDataIterator = event.getMetadata().get(Ros2TakeTimeGraphState.KEY_DATA).iterator(); + isTakeState = stateDataIterator.hasNext() && stateDataIterator.next() instanceof Ros2TakeInstance; + } else if (Ros2ObjectTimeGraphEntryModelType.CLIENT == type) { + Ros2ClientObject clientObject = (Ros2ClientObject) messagesModel.getObject(); + hash = fHasher.hashUnencodedChars(clientObject.getTopicName()).asLong(); + + Iterator<@NonNull Object> stateDataIterator = event.getMetadata().get(Ros2TakeTimeGraphState.KEY_DATA).iterator(); + isTakeState = stateDataIterator.hasNext() && stateDataIterator.next() instanceof Ros2TakeInstance; + } else if(Ros2ObjectTimeGraphEntryModelType.SERVICE == type) { + Ros2ServiceObject serviceObject = (Ros2ServiceObject) messagesModel.getObject(); + hash = fHasher.hashUnencodedChars(serviceObject.getTopicName()).asLong(); + Iterator<@NonNull Object> stateDataIterator = event.getMetadata().get(Ros2TakeTimeGraphState.KEY_DATA).iterator(); isTakeState = stateDataIterator.hasNext() && stateDataIterator.next() instanceof Ros2TakeInstance; } else if (Ros2ObjectTimeGraphEntryModelType.TIMER == type) { @@ -202,6 +216,10 @@ private static int getObjectEntryModelIndex(ITimeEvent event, ITmfTreeDataModel return "message publication"; //$NON-NLS-1$ } else if (Ros2ObjectTimeGraphEntryModelType.SUBSCRIPTION == type) { return "subscription callback/take"; //$NON-NLS-1$ + } else if (Ros2ObjectTimeGraphEntryModelType.CLIENT == type) { + return "request publication or response take"; //$NON-NLS-1$ + } else if (Ros2ObjectTimeGraphEntryModelType.SERVICE == type) { + return "request callback/take or response publication"; //$NON-NLS-1$ } else if (Ros2ObjectTimeGraphEntryModelType.TIMER == type) { return "timer callback"; //$NON-NLS-1$ } @@ -228,6 +246,10 @@ public String getStateTypeName(ITimeGraphEntry entry) { return "Publisher topic"; //$NON-NLS-1$ } else if (Ros2ObjectTimeGraphEntryModelType.SUBSCRIPTION == type) { return "Subscription topic"; //$NON-NLS-1$ + } else if (Ros2ObjectTimeGraphEntryModelType.CLIENT == type) { + return "Service client name"; //$NON-NLS-1$ + } else if (Ros2ObjectTimeGraphEntryModelType.SERVICE == type) { + return "Service server name"; //$NON-NLS-1$ } else if (Ros2ObjectTimeGraphEntryModelType.TIMER == type) { return "Timer period"; //$NON-NLS-1$ } @@ -274,6 +296,34 @@ public Map getEventHoverToolTipInfo(ITimeEvent event) { builder.put("PID", Long.toString(callback.getOwnerHandle().getPid())); //$NON-NLS-1$ builder.put("TID", Long.toString(callback.getTid())); //$NON-NLS-1$ } + } else if (Ros2ObjectTimeGraphEntryModelType.CLIENT == type) { + Object requestPubOrResponseTake = metadata.get(Ros2PubTimeGraphState.KEY_DATA).iterator().next(); + if (requestPubOrResponseTake instanceof Ros2PubInstance) { + Ros2PubInstance pub = (Ros2PubInstance) requestPubOrResponseTake; + builder.put("Request pointer", toHex(pub.getMessage().getPointer())); //$NON-NLS-1$ + builder.put("Source timestamp", FormatTimeUtils.formatTimeAbs(pub.getSourceTimestamp(), Resolution.NANOSEC)); //$NON-NLS-1$ + builder.put("PID", Long.toString(pub.getPublisherHandle().getPid())); //$NON-NLS-1$ + builder.put("TID", Long.toString(pub.getTid())); //$NON-NLS-1$ + } else if (requestPubOrResponseTake instanceof Ros2TakeInstance) { + Ros2TakeInstance take = (Ros2TakeInstance) requestPubOrResponseTake; + builder.put("Response pointer", toHex(take.getMessage().getPointer())); //$NON-NLS-1$ + builder.put("Source timestamp", FormatTimeUtils.formatTimeAbs(take.getSourceTimestamp(), Resolution.NANOSEC)); //$NON-NLS-1$ + builder.put("PID", Long.toString(take.getSubscriptionHandle().getPid())); //$NON-NLS-1$ + builder.put("TID", Long.toString(take.getTid())); //$NON-NLS-1$ + } + } else if (Ros2ObjectTimeGraphEntryModelType.SERVICE == type) { + Object takeOrCallback = metadata.get(Ros2TakeTimeGraphState.KEY_DATA).iterator().next(); + if (takeOrCallback instanceof Ros2TakeInstance) { + Ros2TakeInstance take = (Ros2TakeInstance) takeOrCallback; + builder.put("Request pointer", toHex(take.getMessage().getPointer())); //$NON-NLS-1$ + builder.put("Source timestamp", FormatTimeUtils.formatTimeAbs(take.getSourceTimestamp(), Resolution.NANOSEC)); //$NON-NLS-1$ + builder.put("PID", Long.toString(take.getSubscriptionHandle().getPid())); //$NON-NLS-1$ + builder.put("TID", Long.toString(take.getTid())); //$NON-NLS-1$ + } else if (takeOrCallback instanceof Ros2CallbackInstance) { + Ros2CallbackInstance callback = (Ros2CallbackInstance) takeOrCallback; + builder.put("PID", Long.toString(callback.getOwnerHandle().getPid())); //$NON-NLS-1$ + builder.put("TID", Long.toString(callback.getTid())); //$NON-NLS-1$ + } } else if (Ros2ObjectTimeGraphEntryModelType.TIMER == type) { Ros2CallbackInstance callback = (Ros2CallbackInstance) metadata.get(Ros2CallbackTimeGraphState.KEY_DATA).iterator().next(); builder.put("Intra-process", Boolean.toString(callback.isIntraProcess())); //$NON-NLS-1$