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$