Skip to content

Commit 68c6d78

Browse files
christophebedardbhufmann
authored andcommitted
ros2: support client/service instrumentation (eclipse-tracecompass-incubator#127)
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 <bedard.christophe@gmail.com>
1 parent 2c08243 commit 68c6d78

File tree

18 files changed

+1253
-24
lines changed

18 files changed

+1253
-24
lines changed

tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/Activator.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2TakeInstance;
2323
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2TimerCallbackInstance;
2424
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2CallbackObject;
25+
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ClientObject;
2526
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2NodeObject;
2627
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ObjectHandle;
2728
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2PublisherObject;
29+
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ServiceObject;
2830
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2SubscriptionObject;
2931
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2TimerObject;
3032
import org.eclipse.tracecompass.internal.provisional.statesystem.core.statevalue.CustomStateValue;
@@ -65,6 +67,8 @@ protected void startActions() {
6567
CustomStateValue.registerCustomFactory(Ros2SubscriptionObject.CUSTOM_TYPE_ID, Ros2SubscriptionObject.ROS2_SUBSCRIPTION_OBJECT_VALUE_FACTORY);
6668
CustomStateValue.registerCustomFactory(Ros2TimerObject.CUSTOM_TYPE_ID, Ros2TimerObject.ROS2_TIMER_OBJECT_VALUE_FACTORY);
6769
CustomStateValue.registerCustomFactory(Ros2CallbackObject.CUSTOM_TYPE_ID, Ros2CallbackObject.ROS2_CALLBACK_OBJECT_VALUE_FACTORY);
70+
CustomStateValue.registerCustomFactory(Ros2ClientObject.CUSTOM_TYPE_ID, Ros2ClientObject.ROS2_CLIENT_OBJECT_VALUE_FACTORY);
71+
CustomStateValue.registerCustomFactory(Ros2ServiceObject.CUSTOM_TYPE_ID, Ros2ServiceObject.ROS2_SERVICE_OBJECT_VALUE_FACTORY);
6872
// Instances (for messages analysis)
6973
CustomStateValue.registerCustomFactory(Ros2PubInstance.CUSTOM_TYPE_ID, Ros2PubInstance.ROS2_PUB_INSTANCE_VALUE_FACTORY);
7074
CustomStateValue.registerCustomFactory(Ros2TakeInstance.CUSTOM_TYPE_ID, Ros2TakeInstance.ROS2_TAKE_INSTANCE_VALUE_FACTORY);

tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/Ros2ObjectTimeGraphEntryModel.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
import org.eclipse.jdt.annotation.NonNull;
1818
import org.eclipse.jdt.annotation.Nullable;
19+
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ClientObject;
1920
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2NodeObject;
2021
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2PublisherObject;
22+
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ServiceObject;
2123
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2SubscriptionObject;
2224
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2TimerObject;
2325
import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphEntryModel;
@@ -72,6 +74,10 @@ private static String getEntryModelName(Ros2ObjectTimeGraphEntryModelType type,
7274
return ((Ros2PublisherObject) object).getTopicName();
7375
case SUBSCRIPTION:
7476
return ((Ros2SubscriptionObject) object).getTopicName();
77+
case CLIENT:
78+
return ((Ros2ClientObject) object).getTopicName();
79+
case SERVICE:
80+
return ((Ros2ServiceObject) object).getTopicName();
7581
case TIMER:
7682
return getTimerPeriodAsString((Ros2TimerObject) object);
7783
default:

tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/Ros2ObjectTimeGraphEntryModelType.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ public enum Ros2ObjectTimeGraphEntryModelType {
2525
PUBLISHER,
2626
/** Subscription */
2727
SUBSCRIPTION,
28+
/** Client */
29+
CLIENT,
30+
/** Service */
31+
SERVICE,
2832
/** Timer */
2933
TIMER;
3034
}

tracetypes/org.eclipse.tracecompass.incubator.ros2.core/src/org/eclipse/tracecompass/incubator/internal/ros2/core/analysis/messages/Ros2MessagesDataProvider.java

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Iterator;
2020
import java.util.List;
2121
import java.util.Map;
22+
import java.util.Objects;
2223
import java.util.function.Predicate;
2324

2425
import org.apache.commons.lang3.StringUtils;
@@ -37,9 +38,11 @@
3738
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2SubCallbackInstance;
3839
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2TakeInstance;
3940
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.messages.Ros2TimerCallbackInstance;
41+
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ClientObject;
4042
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2NodeObject;
4143
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ObjectHandle;
4244
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2PublisherObject;
45+
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2ServiceObject;
4346
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2SubscriptionObject;
4447
import org.eclipse.tracecompass.incubator.internal.ros2.core.model.objects.Ros2TimerObject;
4548
import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
@@ -108,7 +111,7 @@ public Ros2MessagesDataProvider(@NonNull ITmfTrace trace, @NonNull Ros2MessagesA
108111
* @author Christophe Bedard
109112
*/
110113
public enum ArrowType {
111-
/** Transport link (pub->sub over network) */
114+
/** Transport link (pub->sub or request/response over network) */
112115
TRANSPORT(1),
113116
/** Callback-publication link */
114117
CALLBACK_PUB(2),
@@ -206,7 +209,7 @@ public int getId() {
206209
if (monitor != null && monitor.isCanceled()) {
207210
return new TimeGraphModel(Collections.emptyList());
208211
}
209-
addRows(rows, entry, intervals, predicates, monitor);
212+
addRows(ss, rows, entry, intervals, predicates, monitor);
210213
}
211214
return new TimeGraphModel(rows);
212215
}
@@ -236,12 +239,39 @@ private static void queryIntervals(@NonNull ITmfStateSystem ss, TreeMultimap<Int
236239
return predicates;
237240
}
238241

239-
private void addRows(List<@NonNull ITimeGraphRowModel> rows, Map.Entry<@NonNull Long, @NonNull Integer> entry, TreeMultimap<Integer, ITmfStateInterval> intervals,
240-
@NonNull Map<@NonNull Integer, @NonNull Predicate<@NonNull Multimap<@NonNull String, @NonNull Object>>> predicates, @Nullable IProgressMonitor monitor) {
242+
private void addRows(@NonNull ITmfStateSystem ss, List<@NonNull ITimeGraphRowModel> rows, Map.Entry<@NonNull Long, @NonNull Integer> entry, TreeMultimap<Integer, ITmfStateInterval> intervals,
243+
@NonNull Map<@NonNull Integer, @NonNull Predicate<@NonNull Multimap<@NonNull String, @NonNull Object>>> predicates, @Nullable IProgressMonitor monitor) throws StateSystemDisposedException {
241244
List<@NonNull ITimeGraphState> eventList = new ArrayList<>();
242245
for (ITmfStateInterval interval : intervals.get(entry.getValue())) {
243246
addRow(entry, predicates, monitor, eventList, interval);
244247
}
248+
249+
/**
250+
* State system intervals for clients & services are stored under two
251+
* attributes (send & take). However, an entry only corresponds to one
252+
* attribute. Since we create entry models for clients & services using
253+
* the "take" attribute, we need to do a simple workaround here to also
254+
* create time graph states for state system intervals under the "send"
255+
* attribute in the same client/service entry model.
256+
*/
257+
int quark = Objects.requireNonNull(entry.getValue());
258+
String name = ss.getAttributeName(quark);
259+
int parentQuark = ss.getParentAttributeQuark(quark);
260+
int grandParentQuark = ss.getParentAttributeQuark(parentQuark);
261+
String grandParentName = grandParentQuark != ITmfStateSystem.ROOT_ATTRIBUTE ? ss.getAttributeName(grandParentQuark) : StringUtils.EMPTY;
262+
// If this is an entry for a "take" attribute
263+
if (name.equals(Ros2MessagesUtil.ClientServiceInstanceType.TAKE.toString()) &&
264+
(grandParentName.equals(Ros2MessagesUtil.LIST_CLIENTS) || grandParentName.equals(Ros2MessagesUtil.LIST_SERVICES))) {
265+
// Find quark for "send" attribute
266+
int clientSendQuark = ss.optQuarkRelative(parentQuark, Ros2MessagesUtil.ClientServiceInstanceType.SEND.toString());
267+
if (ITmfStateSystem.INVALID_ATTRIBUTE != clientSendQuark) {
268+
// Create time graph states under the same ("take") entry model
269+
for (ITmfStateInterval interval : ss.query2D(Collections.singleton(clientSendQuark), ss.getStartTime(), ss.getCurrentEndTime())) {
270+
addRow(entry, predicates, monitor, eventList, interval);
271+
}
272+
}
273+
}
274+
245275
rows.add(new TimeGraphRowModel(entry.getKey(), eventList));
246276
}
247277

@@ -264,7 +294,7 @@ private void addRow(Map.Entry<@NonNull Long, @NonNull Integer> entry, @NonNull M
264294

265295
fHandleToIdMap.put(pubInstance.getPublisherHandle(), entry.getKey());
266296
} else if (valObject instanceof Ros2SubCallbackInstance) {
267-
// Subscription callback
297+
// Subscription callback or service request callback
268298
Ros2SubCallbackInstance subCallbackInstance = (Ros2SubCallbackInstance) valObject;
269299

270300
Ros2TakeInstance takeInstance = subCallbackInstance.getTakeInstance();
@@ -275,6 +305,13 @@ private void addRow(Map.Entry<@NonNull Long, @NonNull Integer> entry, @NonNull M
275305
Ros2CallbackTimeGraphState callbackState = new Ros2CallbackTimeGraphState(callbackInstance);
276306
applyFilterAndAddState(eventList, callbackState, entry.getKey(), predicates, monitor);
277307

308+
fHandleToIdMap.put(takeInstance.getSubscriptionHandle(), entry.getKey());
309+
} else if (valObject instanceof Ros2TakeInstance) {
310+
// Client response take
311+
Ros2TakeInstance takeInstance = (Ros2TakeInstance) valObject;
312+
Ros2TakeTimeGraphState takeState = new Ros2TakeTimeGraphState(takeInstance);
313+
applyFilterAndAddState(eventList, takeState, entry.getKey(), predicates, monitor);
314+
278315
fHandleToIdMap.put(takeInstance.getSubscriptionHandle(), entry.getKey());
279316
} else if (valObject instanceof Ros2TimerCallbackInstance) {
280317
// Timer callback
@@ -311,6 +348,8 @@ private void addChildrenEntryModel(ITmfStateSystem ss, Builder<@NonNull TimeGrap
311348
long childId = getId(child);
312349
String name = ss.getAttributeName(child);
313350
String parentName = quark != ITmfStateSystem.ROOT_ATTRIBUTE ? ss.getAttributeName(quark) : StringUtils.EMPTY;
351+
int grandParentQuark = ss.getParentAttributeQuark(quark);
352+
String grandParentName = grandParentQuark != ITmfStateSystem.ROOT_ATTRIBUTE ? ss.getAttributeName(grandParentQuark) : StringUtils.EMPTY;
314353
if (ITmfStateSystem.ROOT_ATTRIBUTE == quark) {
315354
if (addEntryModel(ss, builder, childId, parentId, child, Ros2ObjectTimeGraphEntryModelType.TRACE)) {
316355
addChildren(ss, builder, child, childId);
@@ -323,9 +362,19 @@ private void addChildrenEntryModel(ITmfStateSystem ss, Builder<@NonNull TimeGrap
323362
addEntryModel(ss, builder, childId, parentId, child, Ros2ObjectTimeGraphEntryModelType.PUBLISHER);
324363
} else if (parentName.equals(Ros2MessagesUtil.LIST_SUBSCRIPTIONS)) {
325364
addEntryModel(ss, builder, childId, parentId, child, Ros2ObjectTimeGraphEntryModelType.SUBSCRIPTION);
365+
} else if (name.equals(Ros2MessagesUtil.ClientServiceInstanceType.TAKE.toString())) {
366+
// Only use the "take" attribute as the entry model
367+
if (grandParentName.equals(Ros2MessagesUtil.LIST_CLIENTS)) {
368+
addEntryModel(ss, builder, childId, parentId, child, Ros2ObjectTimeGraphEntryModelType.CLIENT);
369+
} else if (grandParentName.equals(Ros2MessagesUtil.LIST_SERVICES)) {
370+
addEntryModel(ss, builder, childId, parentId, child, Ros2ObjectTimeGraphEntryModelType.SERVICE);
371+
}
326372
} else if (parentName.equals(Ros2MessagesUtil.LIST_TIMERS)) {
327373
addEntryModel(ss, builder, childId, parentId, child, Ros2ObjectTimeGraphEntryModelType.TIMER);
328-
} else if (name.equals(Ros2MessagesUtil.LIST_NODES) || name.equals(Ros2MessagesUtil.LIST_PUBLISHERS) || name.equals(Ros2MessagesUtil.LIST_SUBSCRIPTIONS) || name.equals(Ros2MessagesUtil.LIST_TIMERS)) {
374+
} else if (name.equals(Ros2MessagesUtil.LIST_NODES) || name.equals(Ros2MessagesUtil.LIST_PUBLISHERS) || name.equals(Ros2MessagesUtil.LIST_SUBSCRIPTIONS) ||
375+
name.equals(Ros2MessagesUtil.LIST_CLIENTS) || parentName.equals(Ros2MessagesUtil.LIST_CLIENTS) ||
376+
name.equals(Ros2MessagesUtil.LIST_SERVICES) || parentName.equals(Ros2MessagesUtil.LIST_SERVICES) ||
377+
name.equals(Ros2MessagesUtil.ClientServiceInstanceType.SEND.toString()) || name.equals(Ros2MessagesUtil.LIST_TIMERS)) {
329378
/**
330379
* Skip this attribute: don't add an entry model, but do proceed
331380
* with children, effectively skipping a layer in the state system
@@ -372,6 +421,22 @@ private boolean addEntryModel(ITmfStateSystem ss, Builder<@NonNull TimeGraphEntr
372421
return true;
373422
}
374423
break;
424+
case CLIENT:
425+
@Nullable
426+
Ros2ClientObject clientObject = getClientObject(ss, quark);
427+
if (null != clientObject) {
428+
builder.add(new Ros2ObjectTimeGraphEntryModel(id, parentId, ss.getStartTime(), ss.getCurrentEndTime(), Ros2ObjectTimeGraphEntryModelType.CLIENT, clientObject));
429+
return true;
430+
}
431+
break;
432+
case SERVICE:
433+
@Nullable
434+
Ros2ServiceObject serviceObject = getServiceObject(ss, quark);
435+
if (null != serviceObject) {
436+
builder.add(new Ros2ObjectTimeGraphEntryModel(id, parentId, ss.getStartTime(), ss.getCurrentEndTime(), Ros2ObjectTimeGraphEntryModelType.SERVICE, serviceObject));
437+
return true;
438+
}
439+
break;
375440
case TIMER:
376441
@Nullable
377442
Ros2TimerObject timerObject = getTimerObject(ss, quark);
@@ -459,6 +524,46 @@ private boolean addEntryModel(ITmfStateSystem ss, Builder<@NonNull TimeGraphEntr
459524
return null;
460525
}
461526

527+
private @Nullable Ros2ClientObject getClientObject(ITmfStateSystem ss, int quark) {
528+
try {
529+
// Get client handle from a time graph state
530+
Iterable<@NonNull ITmfStateInterval> query2d = ss.query2D(Collections.singleton(quark), ss.getStartTime(), ss.getCurrentEndTime());
531+
for (ITmfStateInterval iTmfStateInterval : query2d) {
532+
if(iTmfStateInterval.getValue() instanceof Ros2TakeInstance) {
533+
@Nullable
534+
Ros2TakeInstance responseTakeInstance = (Ros2TakeInstance) iTmfStateInterval.getValue();
535+
if (null != responseTakeInstance) {
536+
return Ros2ObjectsUtil.getClientObjectFromHandle(fObjectsSs, ss.getCurrentEndTime(), responseTakeInstance.getSubscriptionHandle());
537+
}
538+
}
539+
}
540+
} catch (IndexOutOfBoundsException | TimeRangeException | StateSystemDisposedException e) {
541+
// Do nothing
542+
}
543+
Activator.getInstance().logError("could not get client object for entry model"); //$NON-NLS-1$
544+
return null;
545+
}
546+
547+
private @Nullable Ros2ServiceObject getServiceObject(ITmfStateSystem ss, int quark) {
548+
try {
549+
// Get service handle from a time graph state
550+
Iterable<@NonNull ITmfStateInterval> query2d = ss.query2D(Collections.singleton(quark), ss.getStartTime(), ss.getCurrentEndTime());
551+
for (ITmfStateInterval iTmfStateInterval : query2d) {
552+
if (iTmfStateInterval.getValue() instanceof Ros2SubCallbackInstance) {
553+
@Nullable
554+
Ros2SubCallbackInstance subCallbackInstance = (Ros2SubCallbackInstance) iTmfStateInterval.getValue();
555+
if (null != subCallbackInstance) {
556+
return Ros2ObjectsUtil.getServiceObjectFromHandle(fObjectsSs, ss.getCurrentEndTime(), subCallbackInstance.getTakeInstance().getSubscriptionHandle());
557+
}
558+
}
559+
}
560+
} catch (IndexOutOfBoundsException | TimeRangeException | StateSystemDisposedException e) {
561+
// Do nothing
562+
}
563+
Activator.getInstance().logError("could not get service object for entry model"); //$NON-NLS-1$
564+
return null;
565+
}
566+
462567
private @Nullable Ros2TimerObject getTimerObject(ITmfStateSystem ss, int quark) {
463568
try {
464569
// Get timer handle from a time graph state

0 commit comments

Comments
 (0)