Skip to content

Commit

Permalink
ros2: support client/service instrumentation (#127)
Browse files Browse the repository at this point in the history
This adds support for the new client/service (i.e., RPC) instrumentation
in ROS 2, see ros2/ros2_tracing#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 <[email protected]>
  • Loading branch information
christophebedard authored Dec 14, 2024
1 parent 819b9ff commit 4a683e8
Show file tree
Hide file tree
Showing 18 changed files with 1,253 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public enum Ros2ObjectTimeGraphEntryModelType {
PUBLISHER,
/** Subscription */
SUBSCRIPTION,
/** Client */
CLIENT,
/** Service */
SERVICE,
/** Timer */
TIMER;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -236,12 +239,39 @@ private static void queryIntervals(@NonNull ITmfStateSystem ss, TreeMultimap<Int
return predicates;
}

private void addRows(List<@NonNull ITimeGraphRowModel> rows, Map.Entry<@NonNull Long, @NonNull Integer> entry, TreeMultimap<Integer, ITmfStateInterval> 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<Integer, ITmfStateInterval> 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));
}

Expand All @@ -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();
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 4a683e8

Please sign in to comment.