ros: add message flow analysis
From the ROS queues view, the user can select a message from a queue,
which will trigger the message flow analysis. This analysis computes
the path of that message through ROS nodes, including pub/sub queues
and callbacks. It depends on three analyses:
1. (Existing) queues analysis, for all the information about the
state of queues and callbacks.
2. Connections model, which provides a list of ROS connections (e.g.
pub/sub) between nodes. Note: services are not supported yet.
3. Messages transport analysis, itself depending on the connections
model. It uses this model along with a ROS-specific extension of
TmfEventMatching to try to link every message that gets sent over
the network (by a publisher) to a message that is received on the
other side (by a subscriber) in order to build a flow graph.
Its current limitations, which should eventually be addressed, are:
1. At this moment, it assumes that the flow graph is linear, i.e. one
input message in a callback leads to, at most, one published
message.
2. It currently only builds a graph for the selected message, and only
builds it going forward.
Eventually, this should be used/extended to create a ROS-aware
critical path analysis.
Change-Id: I2effd8d74143f5431b713cb034119746fa7708b5
Signed-off-by: Christophe Bedard <bedard.christophe@gmail.com>
Reviewed-on: https://git.eclipse.org/r/140644
Tested-by: CI Bot
Reviewed-by: Genevieve Bastien <gbastien+lttng@versatic.net>
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/META-INF/MANIFEST.MF b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/META-INF/MANIFEST.MF
index 64ddd0c..102b814 100644
--- a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/META-INF/MANIFEST.MF
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/META-INF/MANIFEST.MF
@@ -22,13 +22,20 @@
Export-Package: org.eclipse.tracecompass.incubator.internal.ros.core;x-friends:="org.eclipse.tracecompass.incubator.ros.core.tests,org.eclipse.tracecompass.incubator.ros.ui",
org.eclipse.tracecompass.incubator.internal.ros.core.analysis;x-friends:="org.eclipse.tracecompass.incubator.ros.ui",
org.eclipse.tracecompass.incubator.internal.ros.core.analysis.connections;x-friends:="org.eclipse.tracecompass.incubator.ros.ui",
+ org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow;x-friends:="org.eclipse.tracecompass.incubator.ros.ui",
+ org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow;x-friends:="org.eclipse.tracecompass.incubator.ros.ui",
org.eclipse.tracecompass.incubator.internal.ros.core.analysis.nodes;x-friends:="org.eclipse.tracecompass.incubator.ros.ui",
org.eclipse.tracecompass.incubator.internal.ros.core.analysis.queues;x-friends:="org.eclipse.tracecompass.incubator.ros.ui",
org.eclipse.tracecompass.incubator.internal.ros.core.analysis.tasks;x-friends:="org.eclipse.tracecompass.incubator.ros.ui",
org.eclipse.tracecompass.incubator.internal.ros.core.analysis.timers;x-friends:="org.eclipse.tracecompass.incubator.ros.ui",
+ org.eclipse.tracecompass.incubator.internal.ros.core.signals;x-friends:="org.eclipse.tracecompass.incubator.ros.ui",
org.eclipse.tracecompass.incubator.internal.ros.core.trace;x-friends:="org.eclipse.tracecompass.incubator.ros.ui,org.eclipse.tracecompass.incubator.ros.core.tests",
org.eclipse.tracecompass.incubator.internal.ros.core.trace.layout
Import-Package: com.google.common.base,
com.google.common.collect,
- org.apache.commons.lang3
+ com.google.common.hash,
+ org.apache.commons.lang3,
+ org.eclipse.tracecompass.internal.lttng2.kernel.core,
+ org.eclipse.tracecompass.internal.lttng2.kernel.core.event.matching,
+ org.eclipse.tracecompass.internal.lttng2.kernel.core.trace.layout
Automatic-Module-Name: org.eclipse.tracecompass.incubator.ros.core
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/plugin.properties b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/plugin.properties
index d2b5baf..a5e80f4 100644
--- a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/plugin.properties
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/plugin.properties
@@ -10,7 +10,8 @@
Bundle-Vendor = Eclipse Trace Compass Incubator
Bundle-Name = Trace Compass ROS Core Plug-in (Incubator)
-tracetype.type.ros = ROS Trace
+tracetype.type.ros = ROS Trace (incubator)
+tracetype.experiment.ros = ROS Experiment (incubator)
analysis.ros = ROS (Incubator)
analysis.ros.nodes = ROS Nodes
@@ -18,3 +19,7 @@
analysis.ros.tasks = ROS Tasks
analysis.ros.connections = ROS Connections
analysis.ros.timers = ROS Timers
+
+analysis.ros.model.connections = ROS Connections Model
+analysis.ros.model.messagestransport = ROS Messages Transport Model
+analysis.ros.messageflow = ROS Message Flow
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/plugin.xml b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/plugin.xml
index 75510a0..f040054 100644
--- a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/plugin.xml
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/plugin.xml
@@ -11,6 +11,11 @@
name="%tracetype.type.ros"
trace_type="org.eclipse.tracecompass.incubator.internal.ros.core.trace.RosTrace">
</type>
+ <experiment
+ experiment_type="org.eclipse.tracecompass.incubator.internal.ros.core.trace.RosExperiment"
+ id="org.eclipse.tracecompass.incubator.internal.ros.core.trace.RosExperiment"
+ name="%tracetype.experiment.ros">
+ </experiment>
</extension>
<extension
point="org.eclipse.linuxtools.tmf.core.analysis">
@@ -69,6 +74,36 @@
class="org.eclipse.tracecompass.incubator.internal.ros.core.trace.RosTrace">
</tracetype>
</module>
+ <module
+ analysis_module="org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections.RosConnectionsModelAnalysis"
+ automatic="false"
+ icon="icons/ros.png"
+ id="org.eclipse.tracecompass.incubator.ros.core.analysis.model.connections"
+ name="%analysis.ros.model.connections">
+ <tracetype
+ class="org.eclipse.tracecompass.incubator.internal.ros.core.trace.RosExperiment">
+ </tracetype>
+ </module>
+ <module
+ analysis_module="org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messagestransport.RosMessagesTransportModelAnalysis"
+ icon="icons/ros.png"
+ id="org.eclipse.tracecompass.incubator.ros.core.analysis.model.messagestransport"
+ name="%analysis.ros.model.messagestransport">
+ <tracetype
+ class="org.eclipse.tracecompass.incubator.internal.ros.core.trace.RosExperiment">
+ </tracetype>
+ </module>
+ <module
+ analysis_module="org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow.RosMessageFlowAnalysis"
+ applies_experiment="true"
+ automatic="false"
+ icon="icons/ros.png"
+ id="org.eclipse.tracecompass.incubator.ros.core.analysis.messageflow"
+ name="%analysis.ros.messageflow">
+ <tracetype
+ class="org.eclipse.tracecompass.incubator.internal.ros.core.trace.RosExperiment">
+ </tracetype>
+ </module>
</extension>
<extension
point="org.eclipse.tracecompass.tmf.core.dataprovider">
@@ -92,6 +127,10 @@
class="org.eclipse.tracecompass.incubator.internal.ros.core.analysis.timers.RosTimersDataProviderFactory"
id="org.eclipse.tracecompass.incubator.ros.core.analysis.timers.dataprovider">
</dataProviderFactory>
+ <dataProviderFactory
+ class="org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow.RosMessageFlowDataProviderFactory"
+ id="org.eclipse.tracecompass.incubator.ros.core.analysis.messageflow.dataprovider">
+ </dataProviderFactory>
</extension>
</plugin>
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/connections/RosConnectionsDataProvider.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/connections/RosConnectionsDataProvider.java
index 80358a6..002fc07 100644
--- a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/connections/RosConnectionsDataProvider.java
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/connections/RosConnectionsDataProvider.java
@@ -33,7 +33,6 @@
import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphArrow;
import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphRowModel;
import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphState;
-import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphArrow;
import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphEntryModel;
import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphRowModel;
import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphState;
@@ -42,7 +41,6 @@
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.TreeMultimap;
@@ -58,7 +56,8 @@
/** Data provider suffix ID */
public static final String SUFFIX = ".dataprovider"; //$NON-NLS-1$
- private @NonNull RosConnectionsAnalysis fModule;
+ // TODO use for arrows
+ // private @NonNull RosConnectionsAnalysis fModule;
/**
* Constructor
@@ -70,20 +69,12 @@
*/
public RosConnectionsDataProvider(@NonNull ITmfTrace trace, @NonNull RosConnectionsAnalysis analysisModule) {
super(trace, analysisModule);
- fModule = analysisModule;
+ // fModule = analysisModule;
}
@Override
public @NonNull TmfModelResponse<@NonNull List<@NonNull ITimeGraphArrow>> fetchArrows(@NonNull TimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
- ITmfStateSystem ss = fModule.getStateSystem();
- if (ss == null) {
- return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.ANALYSIS_INITIALIZATION_FAILED);
- }
-
- List<@NonNull ITimeGraphArrow> arrows = Lists.newArrayList();
- filter.getTimesRequested();
- arrows.add(new TimeGraphArrow(0, 0, 0, 0));
- return new TmfModelResponse<>(arrows, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
+ return new TmfModelResponse<>(null, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
@Override
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/RosMessageFlowAnalysis.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/RosMessageFlowAnalysis.java
new file mode 100644
index 0000000..11b9f58
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/RosMessageFlowAnalysis.java
@@ -0,0 +1,129 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow;
+
+import java.util.Collections;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.ros.core.Activator;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.IRosModelProvider;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.RosModelUtils;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow.IRosMessageFlowModel;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow.RosMessageFlowModel;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messagestransport.IRosMessagesTransportModel;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messagestransport.RosMessagesTransportModelAnalysis;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.queues.RosQueuesAnalysis;
+import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
+import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.analysis.TmfAbstractAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
+import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * ROS message flow analysis, which computes the path of a selected message
+ * through ROS nodes, including queues and callbacks
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessageFlowAnalysis extends TmfAbstractAnalysisModule implements IRosModelProvider<IRosMessageFlowModel> {
+
+ /** The ID of this analysis module */
+ public static final @NonNull String ID = "org.eclipse.tracecompass.incubator.ros.core.analysis.messageflow"; //$NON-NLS-1$
+ /** Name for the {@link TargetMessageInfo} parameter */
+ public static final @NonNull String TARGET_MESSAGE_INFO_KEY = "target.message.info"; //$NON-NLS-1$
+
+ private @Nullable IProgressMonitor fMonitor;
+ private @Nullable RosMessageFlowModel fModel = null;
+
+ /**
+ * Constructor
+ */
+ public RosMessageFlowAnalysis() {
+ super();
+ setId(ID);
+ addParameter(TARGET_MESSAGE_INFO_KEY);
+ }
+
+ @Override
+ public @Nullable IRosMessageFlowModel getModel() {
+ return fModel;
+ }
+
+ @Override
+ protected boolean executeAnalysis(@NonNull IProgressMonitor monitor) {
+ // Target message info
+ // Do not execute if it has not been set
+ Object info = getParameter(TARGET_MESSAGE_INFO_KEY);
+ if (info == null || !(info instanceof TargetMessageInfo)) {
+ Activator.getInstance().logError("No target message set; not executing!"); //$NON-NLS-1$
+ return false;
+ }
+ TargetMessageInfo targetMessageInfo = (TargetMessageInfo) info;
+
+ ITmfTrace trace = getTrace();
+ if (!(trace instanceof TmfExperiment)) {
+ throw new IllegalStateException();
+ }
+ // Queues
+ @Nullable
+ ITmfStateSystem queuesStateSystem = RosModelUtils.getStateSystemFromAnalysis(trace, RosQueuesAnalysis.class, RosQueuesAnalysis.getFullAnalysisId());
+ if (queuesStateSystem == null) {
+ return false;
+ }
+ // Messages transport model
+ @Nullable
+ IRosMessagesTransportModel messagesTransportModel = RosModelUtils.getModelFromAnalysis(trace, RosMessagesTransportModelAnalysis.class, RosMessagesTransportModelAnalysis.ID);
+ if (messagesTransportModel == null) {
+ return false;
+ }
+
+ RosMessageFlowModel model = new RosMessageFlowModel(targetMessageInfo, queuesStateSystem, messagesTransportModel);
+ model.generateModel();
+ fModel = model;
+ return fModel.isModelDone() && !monitor.isCanceled();
+ }
+
+ @Override
+ protected void parameterChanged(@NonNull String name) {
+ if (name.equals(TARGET_MESSAGE_INFO_KEY)) {
+ // Re-run analysis
+ cancel();
+ resetAnalysis();
+ schedule();
+ }
+ }
+
+ @Override
+ protected @NonNull Iterable<@NonNull IAnalysisModule> getDependentAnalyses() {
+ ITmfTrace trace = getTrace();
+ if (trace == null) {
+ return Collections.emptySet();
+ }
+ RosQueuesAnalysis queues = TmfTraceUtils.getAnalysisModuleOfClass(trace, RosQueuesAnalysis.class, RosQueuesAnalysis.getFullAnalysisId());
+ RosMessagesTransportModelAnalysis messagesTransportModelAnalysis = TmfTraceUtils.getAnalysisModuleOfClass(trace, RosMessagesTransportModelAnalysis.class, RosMessagesTransportModelAnalysis.ID);
+ if (queues == null || messagesTransportModelAnalysis == null) {
+ return Collections.emptySet();
+ }
+ return ImmutableSet.of(queues, messagesTransportModelAnalysis);
+ }
+
+ @Override
+ protected void canceling() {
+ IProgressMonitor mon = fMonitor;
+ if ((mon != null) && (!mon.isCanceled())) {
+ mon.setCanceled(true);
+ }
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/RosMessageFlowDataProvider.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/RosMessageFlowDataProvider.java
new file mode 100644
index 0000000..bbb40de
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/RosMessageFlowDataProvider.java
@@ -0,0 +1,237 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Predicate;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow.IRosMessageFlowModel;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow.RosMessageFlowSegment;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow.RosMessageFlowSegment.SegmentType;
+import org.eclipse.tracecompass.internal.tmf.core.model.AbstractTmfTraceDataProvider;
+import org.eclipse.tracecompass.internal.tmf.core.model.filters.TimeGraphStateQueryFilter;
+import org.eclipse.tracecompass.tmf.core.model.CommonStatusMessage;
+import org.eclipse.tracecompass.tmf.core.model.filters.SelectionTimeQueryFilter;
+import org.eclipse.tracecompass.tmf.core.model.filters.TimeQueryFilter;
+import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphArrow;
+import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphDataProvider;
+import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphRowModel;
+import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphState;
+import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphArrow;
+import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphEntryModel;
+import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphRowModel;
+import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphState;
+import org.eclipse.tracecompass.tmf.core.response.ITmfResponse;
+import org.eclipse.tracecompass.tmf.core.response.ITmfResponse.Status;
+import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+
+/**
+ * Data provider for the ROS Message Flow view
+ *
+ * @author Christophe Bedard
+ */
+@SuppressWarnings("restriction")
+public class RosMessageFlowDataProvider extends AbstractTmfTraceDataProvider implements ITimeGraphDataProvider<@NonNull TimeGraphEntryModel> {
+
+ /** Data provider suffix ID */
+ public static final @NonNull String SUFFIX = ".dataprovider"; //$NON-NLS-1$
+
+ /** Separator for info in entry model name */
+ public static final @NonNull String SEGMENT_NAME_SEP = ";"; //$NON-NLS-1$
+
+ private static final AtomicLong ATOMIC_LONG = new AtomicLong();
+
+ private @NonNull IRosMessageFlowModel fModel;
+
+ /**
+ * Map for message flow segment <-> id
+ * TODO eventually unflatten this tree
+ */
+ private final BiMap<RosMessageFlowSegment, Long> fSegmentToId = HashBiMap.create();
+
+ /**
+ * Constructor
+ *
+ * @param trace
+ * the trace for this provider
+ * @param model
+ * the message flow model
+ */
+ public RosMessageFlowDataProvider(@NonNull ITmfTrace trace, @NonNull IRosMessageFlowModel model) {
+ super(trace);
+ fModel = model;
+ }
+
+ private long getSegmentId(RosMessageFlowSegment segment) {
+ return fSegmentToId.computeIfAbsent(segment, i -> ATOMIC_LONG.getAndIncrement());
+ }
+
+ @Override
+ public @NonNull TmfModelResponse<@NonNull List<@NonNull TimeGraphEntryModel>> fetchTree(
+ @NonNull TimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
+ if (!fModel.isModelDone()) {
+ return new TmfModelResponse<>(null, Status.RUNNING, CommonStatusMessage.RUNNING);
+ }
+
+ List<@NonNull TimeGraphEntryModel> entries = new ArrayList<>();
+ long rootId = ATOMIC_LONG.getAndIncrement();
+ entries.add(new TimeGraphEntryModel(rootId, -1, String.valueOf(getTrace().getName()), filter.getStart(), filter.getEnd()));
+ RosMessageFlowSegment firstSegment = fModel.getFirstSegment();
+ addTreeChildren(entries, firstSegment, rootId);
+ return new TmfModelResponse<>(entries, Status.COMPLETED, CommonStatusMessage.COMPLETED);
+ }
+
+ private void addTreeChildren(List<@NonNull TimeGraphEntryModel> entries, RosMessageFlowSegment segment, long parentId) {
+ // TODO eventually find a better way to represent this (not flat/similar
+ // to queues view)
+ long entryId = getSegmentId(segment);
+ entries.add(new RosMessageFlowSegmentEntryModel(entryId, parentId, segment.getStartTime(), segment.getEndTime(), segment));
+
+ Collection<RosMessageFlowSegment> next = segment.getNext();
+ for (RosMessageFlowSegment n : next) {
+ addTreeChildren(entries, n, parentId);
+ }
+ }
+
+ @Override
+ public @NonNull TmfModelResponse<@NonNull List<@NonNull ITimeGraphRowModel>> fetchRowModel(
+ @NonNull SelectionTimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
+ if (!fModel.isModelDone()) {
+ return new TmfModelResponse<>(null, Status.RUNNING, CommonStatusMessage.RUNNING);
+ }
+
+ @NonNull Map<@NonNull Integer, @NonNull Predicate<@NonNull Multimap<@NonNull String, @NonNull String>>> predicates = new HashMap<>();
+ if (filter instanceof TimeGraphStateQueryFilter) {
+ TimeGraphStateQueryFilter timeEventFilter = (TimeGraphStateQueryFilter) filter;
+ predicates.putAll(computeRegexPredicate(timeEventFilter));
+ }
+
+ List<@NonNull ITimeGraphRowModel> rows = new ArrayList<>();
+ RosMessageFlowSegment firstSegment = fModel.getFirstSegment();
+ addRowModels(rows, firstSegment, predicates, monitor);
+ return new TmfModelResponse<>(rows, Status.COMPLETED, CommonStatusMessage.COMPLETED);
+ }
+
+ private void addRowModels(
+ List<@NonNull ITimeGraphRowModel> rows,
+ RosMessageFlowSegment segment,
+ @NonNull Map<@NonNull Integer, @NonNull Predicate<@NonNull Multimap<@NonNull String, @NonNull String>>> predicates,
+ @Nullable IProgressMonitor monitor) {
+ @NonNull List<@NonNull ITimeGraphState> eventList = new ArrayList<>();
+
+ // TODO eventually find a better way
+ long entryId = getSegmentId(segment);
+ long startTime = segment.getStartTime();
+ long duration = segment.getEndTime() - startTime + 1;
+ TimeGraphState state = new TimeGraphState(startTime, duration, getMatchingSegmentState(segment.getType()));
+ applyFilterAndAddState(eventList, state, entryId, predicates, monitor);
+ rows.add(new TimeGraphRowModel(entryId, eventList));
+
+ Collection<RosMessageFlowSegment> next = segment.getNext();
+ for (RosMessageFlowSegment n : next) {
+ addRowModels(rows, n, predicates, monitor);
+ }
+ }
+
+ private static int getMatchingSegmentState(SegmentType type) {
+ // See RosMessageFlowPresentationProvider.State
+ switch (type) {
+ case PUB_QUEUE:
+ return 0;
+ case SUB_QUEUE:
+ return 1;
+ case SUB_CALLBACK:
+ return 2;
+ case INVALID:
+ break;
+ default:
+ break;
+ }
+ return 4;
+ }
+
+ @Override
+ public @NonNull TmfModelResponse<@NonNull List<@NonNull ITimeGraphArrow>> fetchArrows(@NonNull TimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
+ if (!fModel.isModelDone()) {
+ return new TmfModelResponse<>(null, Status.RUNNING, CommonStatusMessage.RUNNING);
+ }
+
+ return new TmfModelResponse<>(getArrows(filter.getStart(), filter.getEnd()), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
+ }
+
+ private @Nullable List<@NonNull ITimeGraphArrow> getArrows(long startTime, long endTime) {
+ List<@NonNull ITimeGraphArrow> arrows = Lists.newArrayList();
+ RosMessageFlowSegment firstSegment = fModel.getFirstSegment();
+ addArrows(firstSegment, arrows, startTime, endTime);
+ return arrows;
+ }
+
+ private void addArrows(RosMessageFlowSegment parent, List<@NonNull ITimeGraphArrow> arrows, long startTime, long endTime) {
+ Collection<RosMessageFlowSegment> next = parent.getNext();
+ for (RosMessageFlowSegment n : next) {
+ if (parent.getEndTime() <= endTime && n.getStartTime() >= startTime) {
+ addArrow(parent, n, arrows);
+ }
+ addArrows(n, arrows, startTime, endTime);
+ }
+ }
+
+ private void addArrow(RosMessageFlowSegment parent, RosMessageFlowSegment child, List<@NonNull ITimeGraphArrow> arrows) {
+ long sourceId = getSegmentId(parent);
+ long destinationId = getSegmentId(child);
+ long time = parent.getEndTime();
+ long duration = child.getStartTime() - parent.getEndTime();
+ arrows.add(new TimeGraphArrow(sourceId, destinationId, time, duration, getMatchingArrowState(parent.getType(), child.getType())));
+ }
+
+ private static int getMatchingArrowState(SegmentType source, SegmentType destination) {
+ // See RosMessageFlowPresentationProvider.State
+ if (source.equals(SegmentType.PUB_QUEUE) && destination.equals(SegmentType.SUB_QUEUE)) {
+ return 4;
+ } else if (source.equals(SegmentType.SUB_QUEUE) && destination.equals(SegmentType.SUB_CALLBACK)) {
+ return 5;
+ } else if (destination.equals(SegmentType.PUB_QUEUE)) {
+ return 6;
+ }
+ return 7;
+ }
+
+ @Override
+ public @NonNull TmfModelResponse<@NonNull Map<@NonNull String, @NonNull String>> fetchTooltip(@NonNull SelectionTimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
+ // TODO
+ return new TmfModelResponse<>(null, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
+ }
+
+ @Override
+ public @NonNull String getId() {
+ return getFullDataProviderId();
+ }
+
+ /**
+ * @return the full dataprovider ID
+ */
+ public static @NonNull String getFullDataProviderId() {
+ return RosMessageFlowAnalysis.ID + SUFFIX;
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/RosMessageFlowDataProviderFactory.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/RosMessageFlowDataProviderFactory.java
new file mode 100644
index 0000000..97aee31
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/RosMessageFlowDataProviderFactory.java
@@ -0,0 +1,84 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow.IRosMessageFlowModel;
+import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderFactory;
+import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataModel;
+import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataProvider;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
+import org.eclipse.tracecompass.tmf.core.signal.TmfStartAnalysisSignal;
+import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+
+/**
+ * {@link RosMessageFlowDataProvider} factory
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessageFlowDataProviderFactory implements IDataProviderFactory {
+
+ private final Map<ITmfTrace, RosMessageFlowAnalysis> fMap = new HashMap<>();
+
+ /**
+ * Constructor, registers the module with the {@link TmfSignalManager}
+ */
+ public RosMessageFlowDataProviderFactory() {
+ TmfSignalManager.register(this);
+ }
+
+ @Override
+ public @Nullable ITmfTreeDataProvider<? extends ITmfTreeDataModel> createProvider(@NonNull ITmfTrace trace) {
+ RosMessageFlowAnalysis module = fMap.remove(trace);
+ if (module == null) {
+ return null;
+ }
+ IRosMessageFlowModel model = module.getModel();
+ if (model == null) {
+ return null;
+ }
+ return new RosMessageFlowDataProvider(trace, model);
+ }
+
+ /**
+ * {@link TmfSignalHandler} for when {@link RosMessageFlowAnalysis} is
+ * started, as the analysis is not registered with the trace, we use this to
+ * know to associate a {@link RosMessageFlowAnalysis} to a trace.
+ *
+ * @param startAnalysisSignal
+ * analysis started signal
+ */
+ @TmfSignalHandler
+ public synchronized void analysisStarted(TmfStartAnalysisSignal startAnalysisSignal) {
+ IAnalysisModule module = startAnalysisSignal.getAnalysisModule();
+ if (module instanceof RosMessageFlowAnalysis) {
+ RosMessageFlowAnalysis analysis = (RosMessageFlowAnalysis) module;
+ fMap.put(analysis.getTrace(), analysis);
+ }
+ }
+
+ /**
+ * Remove the closed trace's analysis to avoid resource leaks
+ *
+ * @param traceClosedSignal
+ * the TMF trace closed signal
+ */
+ @TmfSignalHandler
+ public synchronized void traceClosed(TmfTraceClosedSignal traceClosedSignal) {
+ fMap.remove(traceClosedSignal.getTrace());
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/RosMessageFlowSegmentEntryModel.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/RosMessageFlowSegmentEntryModel.java
new file mode 100644
index 0000000..c5be10d
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/RosMessageFlowSegmentEntryModel.java
@@ -0,0 +1,51 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow.RosMessageFlowSegment;
+import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphEntryModel;
+
+/**
+ * Extension of {@link TimeGraphEntryModel} to keep a reference to the
+ * corresponding {@link RosMessageFlowSegment}
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessageFlowSegmentEntryModel extends TimeGraphEntryModel {
+
+ private final RosMessageFlowSegment fSegment;
+
+ /**
+ * Constructor
+ *
+ * @param id
+ * Entry ID
+ * @param parentId
+ * Parent ID
+ * @param startTime
+ * Start time
+ * @param endTime
+ * End time
+ * @param segment
+ * the corresponding segment
+ */
+ public RosMessageFlowSegmentEntryModel(long id, long parentId, long startTime, long endTime, RosMessageFlowSegment segment) {
+ super(id, parentId, StringUtils.EMPTY, startTime, endTime);
+ fSegment = segment;
+ }
+
+ /**
+ * @return the segment
+ */
+ public RosMessageFlowSegment getSegment() {
+ return fSegment;
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/TargetMessageInfo.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/TargetMessageInfo.java
new file mode 100644
index 0000000..7974d5a
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/messageflow/TargetMessageInfo.java
@@ -0,0 +1,131 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow;
+
+/**
+ * Represents a target message to be followed. This contains the necessary
+ * information to find the actual corresponding message.
+ *
+ * @author Christophe Bedard
+ */
+public class TargetMessageInfo {
+
+ private final String fNode;
+ private final String fTopic;
+ private final RosQueueType fQueueType;
+ private final int fQueuePosition;
+ private final long fMsgRef;
+ private final long fSelectedTimestamp;
+ private final long fMsgTimestamp;
+
+ /**
+ * Type of the queue for the selected message
+ *
+ * @author Christophe Bedard
+ */
+ public enum RosQueueType {
+ /**
+ * Subscriber queue
+ */
+ SUB,
+ /**
+ * Publisher queue
+ */
+ PUB
+ }
+
+ /**
+ * Constructor
+ *
+ * @param node
+ * the node name
+ * @param topic
+ * the topic name
+ * @param queueType
+ * the type of the queue in which this message is
+ * @param queuePosition
+ * the message's position in the queue
+ * @param msgRef
+ * the message reference (0x0123abc)
+ * @param selectedTimestamp
+ * the selected timestamp
+ * @param msgTimestamp
+ * the selected message's begin timestamp
+ */
+ public TargetMessageInfo(String node, String topic, RosQueueType queueType, int queuePosition, String msgRef, long selectedTimestamp, long msgTimestamp) {
+ fNode = node;
+ fTopic = topic;
+ fQueueType = queueType;
+ fQueuePosition = queuePosition;
+ fMsgRef = Long.decode(msgRef);
+ fSelectedTimestamp = selectedTimestamp;
+ fMsgTimestamp = msgTimestamp;
+ }
+
+ /**
+ * @return the node name
+ */
+ public String getNode() {
+ return fNode;
+ }
+
+ /**
+ * @return the topic name
+ */
+ public String getTopic() {
+ return fTopic;
+ }
+
+ /**
+ * @return the type of the queue in which this message is
+ */
+ public RosQueueType getQueueType() {
+ return fQueueType;
+ }
+
+ /**
+ * @return the message's position in the queue
+ */
+ public int getQueuePosition() {
+ return fQueuePosition;
+ }
+
+ /**
+ * @return the message reference
+ */
+ public long getMsgRef() {
+ return fMsgRef;
+ }
+
+ /**
+ * @return the selected timestamp
+ */
+ public long getSelectedTimestamp() {
+ return fSelectedTimestamp;
+ }
+
+ /**
+ * @return the selected message's begin timestamp
+ */
+ public long getMsgTimestamp() {
+ return fMsgTimestamp;
+ }
+
+ @Override
+ public String toString() {
+ return "TargetMessageInfo: " //$NON-NLS-1$
+ + fNode + ", " //$NON-NLS-1$
+ + fTopic + "(" + fQueueType.name() + "):" //$NON-NLS-1$//$NON-NLS-2$
+ + "0x" + Long.toHexString(fMsgRef) //$NON-NLS-1$
+ + " @ " + fQueuePosition //$NON-NLS-1$
+ + ", " + fMsgTimestamp //$NON-NLS-1$
+ + " | " + fSelectedTimestamp; //$NON-NLS-1$
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/IRosModel.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/IRosModel.java
new file mode 100644
index 0000000..be4e67e
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/IRosModel.java
@@ -0,0 +1,23 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model;
+
+/**
+ * Interface for ROS models
+ *
+ * @author Christophe Bedard
+ */
+public interface IRosModel {
+
+ /**
+ * Launch the process of generating the model
+ */
+ void generateModel();
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/IRosModelProvider.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/IRosModelProvider.java
new file mode 100644
index 0000000..66c1f09
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/IRosModelProvider.java
@@ -0,0 +1,30 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Interface for a model provider, which is an analysis that takes information
+ * from other model(s), existing state system(s), and/or trace event(s), and
+ * generates a model
+ *
+ * @param <M>
+ * the ROS model it provides
+ * @author Christophe Bedard
+ */
+public interface IRosModelProvider<M extends IRosModel> {
+
+ /**
+ * @return the model, or {@code null} if it hasn't been generated yet
+ */
+ @Nullable
+ M getModel();
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/RosModelUtils.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/RosModelUtils.java
new file mode 100644
index 0000000..7d838e4
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/RosModelUtils.java
@@ -0,0 +1,98 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.ros.core.Activator;
+import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
+import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.statesystem.TmfStateSystemAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
+
+/**
+ * Utils for ROS models (analyses and models themselves)
+ *
+ * @author Christophe Bedard
+ */
+public class RosModelUtils {
+
+ /**
+ * Get model from analysis after scheduling and waiting for it to complete
+ *
+ * @param trace
+ * the trace from which to get the analysis and model
+ * @param moduleClass
+ * the analysis module class
+ * @param id
+ * the ID of the analysis module
+ * @param <M>
+ * the model
+ * @param <P>
+ * the analysis that builds/provides the model
+ * @return the model, or {@code null} if it failed
+ */
+ public static <M extends IRosModel, P extends IRosModelProvider<M> & IAnalysisModule> @Nullable M getModelFromAnalysis(
+ @NonNull ITmfTrace trace,
+ @NonNull Class<@NonNull P> moduleClass,
+ @NonNull String id) {
+ @Nullable
+ P modelAnalysis = TmfTraceUtils.getAnalysisModuleOfClass(trace, moduleClass, id);
+ if (modelAnalysis == null) {
+ throw new IllegalStateException("There should be an analysis for this class"); //$NON-NLS-1$
+ }
+ modelAnalysis.schedule();
+ if (!modelAnalysis.waitForCompletion()) {
+ throw new IllegalStateException("Problem waiting for the analysis"); //$NON-NLS-1$
+ }
+ @Nullable
+ M model = modelAnalysis.getModel();
+ if (model == null) {
+ Activator.getInstance().logError("Model is null"); //$NON-NLS-1$
+ }
+ return model;
+ }
+
+ /**
+ * Get state system from analysis after scheduling and waiting for it to
+ * complete
+ *
+ * @param trace
+ * the trace from which to get the analysis and model
+ * @param moduleClass
+ * the analysis module class
+ * @param id
+ * the ID of the analysis module
+ * @param <A>
+ * the analysis that provides the state system
+ * @return the state system, or {@code null} if it failed
+ */
+ public static <A extends TmfStateSystemAnalysisModule> @Nullable ITmfStateSystem getStateSystemFromAnalysis(
+ @NonNull ITmfTrace trace,
+ @NonNull Class<@NonNull A> moduleClass,
+ @NonNull String id) {
+ @Nullable
+ A analysis = TmfTraceUtils.getAnalysisModuleOfClass(trace, moduleClass, id);
+ if (analysis == null) {
+ throw new IllegalStateException("There should be an analysis for this class"); //$NON-NLS-1$
+ }
+ analysis.schedule();
+ if (!analysis.waitForCompletion()) {
+ throw new IllegalStateException("Problem waiting for the analysis"); //$NON-NLS-1$
+ }
+ @Nullable
+ ITmfStateSystem ss = analysis.getStateSystem();
+ if (ss == null) {
+ Activator.getInstance().logError("State system is null"); //$NON-NLS-1$
+ }
+ return ss;
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/ConnectionEndpoint.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/ConnectionEndpoint.java
new file mode 100644
index 0000000..623b136
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/ConnectionEndpoint.java
@@ -0,0 +1,123 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Represents a connection endpoint with an address and a port. IPv4-specific
+ * for now.
+ *
+ * @author Christophe Bedard
+ */
+public class ConnectionEndpoint {
+
+ private static final @NonNull String ADDRESS_SEP = "."; //$NON-NLS-1$
+ private static final @NonNull String PORT_SEP = ":"; //$NON-NLS-1$
+
+ private final long[] fAddress;
+ private final long fPort;
+
+ /**
+ * Constructor
+ *
+ * @param address
+ * the address
+ * @param port
+ * the port
+ */
+ public ConnectionEndpoint(long[] address, long port) {
+ fAddress = address;
+ fPort = port;
+ }
+
+ /**
+ * @return the address
+ */
+ public long[] getAddress() {
+ return fAddress;
+ }
+
+ /**
+ * @return the port
+ */
+ public long getPort() {
+ return fPort;
+ }
+
+ /**
+ * Format address and port
+ *
+ * @param address
+ * the address
+ * @param port
+ * the port
+ * @return the address and port formated like 1.2.3.4:1234
+ */
+ public static String formatAddressPort(long[] address, long port) {
+ return StringUtils.join(ArrayUtils.toObject(address), ADDRESS_SEP) + PORT_SEP + String.valueOf(port);
+ }
+
+ @Override
+ public String toString() {
+ return formatAddressPort(fAddress, fPort);
+ }
+
+ /**
+ * Create a {@link ConnectionEndpoint} from a string like "1.2.3.4:1234"
+ *
+ * @param f
+ * the string
+ * @return the {@link ConnectionEndpoint}, or {@code null} if invalid
+ */
+ public static @Nullable ConnectionEndpoint fromStringFormat(String f) {
+ @NonNull String[] split = f.split(PORT_SEP);
+ if (split.length != 2) {
+ return null;
+ }
+
+ @NonNull String[] addressStr = split[0].split(Objects.requireNonNull(Pattern.quote(ADDRESS_SEP)));
+ long[] address = new long[addressStr.length];
+ for (int i = 0; i < addressStr.length; ++i) {
+ address[i] = Long.parseLong(addressStr[i]);
+ }
+ long port = Long.parseLong(split[1]);
+
+ return new ConnectionEndpoint(address, port);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Objects.hash(fPort);
+ result = prime * result + Arrays.hashCode(fAddress);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ConnectionEndpoint)) {
+ return false;
+ }
+ ConnectionEndpoint other = (ConnectionEndpoint) obj;
+ return Arrays.equals(fAddress, other.fAddress) && fPort == other.fPort;
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/IRosConnectionsModel.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/IRosConnectionsModel.java
new file mode 100644
index 0000000..f8d7c1b
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/IRosConnectionsModel.java
@@ -0,0 +1,82 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.IRosModel;
+
+/**
+ * Interface that represents the model for ROS connections, which are network
+ * connections between publishers and subscribers
+ *
+ * @author Christophe Bedard
+ */
+public interface IRosConnectionsModel extends IRosModel {
+
+ /**
+ * Get all connections
+ *
+ * @return an unmodifiable view of the {@link RosConnection}s, or
+ * {@code null} if it hasn't been created
+ */
+ @Nullable
+ Collection<RosConnection> getConnections();
+
+ /**
+ * Get a node's connections
+ *
+ * @param nodeName
+ * the node name
+ * @return the node's {@link RosConnection}s
+ */
+ List<RosConnection> getNodeConnections(String nodeName);
+
+ /**
+ * Get connection corresponding to an endpoint and a topic name
+ *
+ * @param endpoint
+ * the connection endpoint
+ * @param topicName
+ * the topic name
+ * @return the corresponding {@link RosConnection}, or {@code null} if it
+ * doesn't exist
+ */
+ @Nullable
+ RosConnection getConnection(ConnectionEndpoint endpoint, String topicName);
+
+ /**
+ * Get connection corresponding to a node and a published topic
+ *
+ * @param pubNodeName
+ * the publisher node name
+ * @param topicName
+ * the topic name
+ * @return the corresponding {@link RosConnection}, or {@code null} if it
+ * doesn't exist
+ */
+ @Nullable
+ RosConnection getConnectionFromNodePublishingOnTopic(String pubNodeName, String topicName);
+
+ /**
+ * Get connection corresponding to a source+destination endpoints pair
+ *
+ * @param source
+ * the source endpoint
+ * @param destination
+ * the destination endpoint
+ * @return the corresponding {@link RosConnection}, or {@code null} if it
+ * doesn't exist
+ */
+ @Nullable
+ RosConnection getConnectionBetweenEndpoints(ConnectionEndpoint source, ConnectionEndpoint destination);
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/RosConnection.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/RosConnection.java
new file mode 100644
index 0000000..ee49f6f
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/RosConnection.java
@@ -0,0 +1,83 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections;
+
+import java.util.Objects;
+
+/**
+ * Represents a connection between two ROS nodes. Right now this only applies to
+ * a publisher and a subscriber.
+ *
+ * @author Christophe Bedard
+ */
+public class RosConnection {
+
+ private final RosConnectionEndpoint fPub;
+ private final RosConnectionEndpoint fSub;
+
+ /**
+ * Constructor
+ *
+ * @param pub
+ * the publisher's connection info
+ * @param sub
+ * the subscriber's connection info
+ */
+ public RosConnection(RosConnectionEndpoint pub, RosConnectionEndpoint sub) {
+ fPub = pub;
+ fSub = sub;
+ }
+
+ /**
+ * @return the publisher connection
+ */
+ public RosConnectionEndpoint getPub() {
+ return fPub;
+ }
+
+ /**
+ * @return the subscriber connection
+ */
+ public RosConnectionEndpoint getSub() {
+ return fSub;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fPub, fSub);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof RosConnection)) {
+ return false;
+ }
+ RosConnection other = (RosConnection) obj;
+ return Objects.equals(fPub, other.fPub) && Objects.equals(fSub, other.fSub);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("RosConnection"); //$NON-NLS-1$
+ b.append("\n"); //$NON-NLS-1$
+ b.append("\tPUB: " + fPub.toString()); //$NON-NLS-1$
+ b.append("\n"); //$NON-NLS-1$
+ b.append("\tSUB: " + fSub.toString()); //$NON-NLS-1$
+ b.append("\n"); //$NON-NLS-1$
+ return b.toString();
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/RosConnectionEndpoint.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/RosConnectionEndpoint.java
new file mode 100644
index 0000000..bd4f145
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/RosConnectionEndpoint.java
@@ -0,0 +1,95 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections;
+
+import java.util.Objects;
+
+/**
+ * Represents one side of a connection between two ROS nodes
+ *
+ * TODO add connection end time
+ *
+ * @author Christophe Bedard
+ */
+public class RosConnectionEndpoint {
+
+ private final String fTopicName;
+ private final String fNodeName;
+ private final ConnectionEndpoint fEndpoint;
+ private final long fStartTime;
+
+ /**
+ * Constructor
+ *
+ * @param topic
+ * the topic name
+ * @param node
+ * the node name
+ * @param endpoint
+ * the endpoint
+ * @param time
+ * the connection start time
+ */
+ public RosConnectionEndpoint(String topic, String node, ConnectionEndpoint endpoint, long time) {
+ fTopicName = topic;
+ fNodeName = node;
+ fEndpoint = endpoint;
+ fStartTime = time;
+ }
+
+ /**
+ * @return the topic name
+ */
+ public String getTopicName() {
+ return fTopicName;
+ }
+
+ /**
+ * @return the node name
+ */
+ public String getNodeName() {
+ return fNodeName;
+ }
+
+ /**
+ * @return the network endpoint
+ */
+ public ConnectionEndpoint getEndpoint() {
+ return fEndpoint;
+ }
+
+ /**
+ * @return the connection start time
+ */
+ public long getStartTime() {
+ return fStartTime;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fTopicName, fNodeName, fEndpoint, fStartTime);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("\tRosConnectionEndpoint"); //$NON-NLS-1$
+ b.append("\n"); //$NON-NLS-1$
+ b.append("\t\ttopic =" + fTopicName); //$NON-NLS-1$
+ b.append("\n"); //$NON-NLS-1$
+ b.append("\t\thost:port =" + fEndpoint.toString()); //$NON-NLS-1$
+ b.append("\n"); //$NON-NLS-1$
+ b.append("\t\tnode =" + fNodeName); //$NON-NLS-1$
+ b.append("\n"); //$NON-NLS-1$
+ b.append("\t\ttime =" + fStartTime); //$NON-NLS-1$
+ b.append("\n"); //$NON-NLS-1$
+ return b.toString();
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/RosConnectionsModel.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/RosConnectionsModel.java
new file mode 100644
index 0000000..0d7e02e
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/RosConnectionsModel.java
@@ -0,0 +1,234 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.ros.core.Activator;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.connections.RosConnectionsStateProvider;
+import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
+import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
+import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
+import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
+import org.eclipse.tracecompass.tmf.core.util.Pair;
+
+import com.google.common.collect.Lists;
+
+/**
+ * {@link IRosConnectionsModel} implementation based on a complete connections
+ * analysis state system
+ *
+ * TODO support service connections
+ *
+ * @author Christophe Bedard
+ */
+public class RosConnectionsModel implements IRosConnectionsModel {
+
+ private static final String TYPE_PUB_LINK = "TransportPublisherLink"; //$NON-NLS-1$
+ private static final String TYPE_SUB_LINK = "TransportSubscriberLink"; //$NON-NLS-1$
+
+ private List<RosConnection> fConnections = null;
+ private final @NonNull ITmfStateSystem fSs;
+ private final @NonNull IProgressMonitor fMon;
+
+ /**
+ * Constructor
+ *
+ * @param stateSystem
+ * the state system
+ * @param monitor
+ * the progress monitor
+ */
+ public RosConnectionsModel(@NonNull ITmfStateSystem stateSystem, @NonNull IProgressMonitor monitor) {
+ fSs = stateSystem;
+ fMon = monitor;
+ }
+
+ @Override
+ public void generateModel() {
+ try {
+ List<RosConnectionEndpoint> connectionsInfoPub = new ArrayList<>();
+ // (Remote endpoint, topic name) --> local subscriber connection
+ // endpoint
+ Map<Pair<ConnectionEndpoint, String>, RosConnectionEndpoint> connectionsInfoSub = new HashMap<>();
+
+ if (fMon.isCanceled()) {
+ return;
+ }
+ gatherConnectionInfo(fSs, connectionsInfoPub, connectionsInfoSub);
+ if (fMon.isCanceled()) {
+ return;
+ }
+ fConnections = linkConnectionInfo(connectionsInfoPub, connectionsInfoSub);
+
+ if (connectionsInfoPub.size() != connectionsInfoSub.size()) {
+ Activator.getInstance().logWarning("Number of publisher connection info and subscriber connection info do not match!"); //$NON-NLS-1$
+ }
+ } catch (StateSystemDisposedException e) {
+ // Ignore
+ }
+ }
+
+ /**
+ * Go through state system and gather connection information (from each
+ * side).
+ *
+ * @param ss
+ * the state system
+ * @param connectionsInfoPub
+ * the list to put publisher connection endpoints into
+ * @param connectionsInfoSub
+ * the map to put subscriber connection endpoints into
+ * @throws StateSystemDisposedException
+ * @throws TimeRangeException
+ * @throws IndexOutOfBoundsException
+ */
+ private static void gatherConnectionInfo(ITmfStateSystem ss, List<RosConnectionEndpoint> connectionsInfoPub, Map<Pair<ConnectionEndpoint, String>, RosConnectionEndpoint> connectionsInfoSub)
+ throws IndexOutOfBoundsException, TimeRangeException, StateSystemDisposedException {
+ // Gather info about all connections
+ List<@NonNull Integer> nodeQuarks = ss.getSubAttributes(ITmfStateSystem.ROOT_ATTRIBUTE, false);
+ for (@NonNull Integer nodeQuark : nodeQuarks) {
+ String nodeName = ss.getAttributeName(nodeQuark);
+ List<@NonNull Integer> typeQuarks = ss.getSubAttributes(nodeQuark, false);
+ for (@NonNull Integer typeQuark : typeQuarks) {
+ String type = ss.getAttributeName(typeQuark);
+ if (!(type.equals(TYPE_PUB_LINK) || type.equals(TYPE_SUB_LINK))) {
+ // TODO support service connections
+ continue;
+ }
+
+ // Publisher of the following topics if this is a subscriber
+ // link
+ boolean isPubSide = type.equals(TYPE_SUB_LINK);
+ List<@NonNull Integer> topicQuarks = ss.getSubAttributes(typeQuark, false);
+ for (@NonNull Integer topicQuark : topicQuarks) {
+ String topicName = ss.getAttributeName(topicQuark);
+ List<@NonNull Integer> connectionQuarks = ss.getSubAttributes(topicQuark, false);
+ for (@NonNull Integer connectionQuark : connectionQuarks) {
+ Iterable<@NonNull ITmfStateInterval> connectionIntervalsIter = ss.query2D(Collections.singleton(connectionQuark), ss.getStartTime(), ss.getCurrentEndTime());
+ List<@NonNull ITmfStateInterval> connectionIntervals = Lists.newArrayList(connectionIntervalsIter);
+ // First interval should be null, then the second
+ // interval should have the host:port combo
+ if (!(connectionIntervals.size() >= 1)) {
+ continue;
+ }
+ ITmfStateInterval connectionInterval = connectionIntervals.get(1);
+ String hostsPorts = connectionInterval.getValueString();
+ // Local and remote host:port (in that order) with a
+ // separator in between
+ @NonNull String[] localRemote = hostsPorts.split(RosConnectionsStateProvider.CONNECTION_HOST_SEPARATOR);
+ String localHostPort = localRemote[0];
+ ConnectionEndpoint localEndpoint = ConnectionEndpoint.fromStringFormat(localHostPort);
+ RosConnectionEndpoint local = new RosConnectionEndpoint(topicName, nodeName, localEndpoint, connectionInterval.getStartTime());
+ if (isPubSide) {
+ connectionsInfoPub.add(local);
+ } else {
+ String remoteHostPort = localRemote[1];
+ ConnectionEndpoint remoteEndpoint = ConnectionEndpoint.fromStringFormat(remoteHostPort);
+ connectionsInfoSub.put(new Pair<>(remoteEndpoint, topicName), local);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Link matching {@link RosConnectionEndpoint} to make up a
+ * {@link RosConnection}
+ *
+ * @param connectionsInfoPub
+ * the list of publisher connection endpoints
+ * @param connectionsInfoSub
+ * the subscriber connection info map
+ * @return the resulting list of {@link RosConnection}
+ */
+ private static List<RosConnection> linkConnectionInfo(List<RosConnectionEndpoint> connectionsInfoPub, Map<Pair<ConnectionEndpoint, String>, RosConnectionEndpoint> connectionsInfoSub) {
+ List<RosConnection> connections = new ArrayList<>();
+ for (RosConnectionEndpoint pubInfo : connectionsInfoPub) {
+ // Both of the following have to match, since the same
+ // Transport*Link can be used for two different pub/sub connections
+ // (i.e. the same host:port can be used for two different topics):
+ // - remote host:port on the subscriber's side == local host:port on
+ // the publisher's side
+ // - topic name on the subscriber's side == topic name on the
+ // publisher's side
+ RosConnectionEndpoint subInfo = connectionsInfoSub.get(new Pair<>(pubInfo.getEndpoint(), pubInfo.getTopicName()));
+ if (subInfo == null) {
+ Activator.getInstance().logError("No connection info match!"); //$NON-NLS-1$
+ continue;
+ }
+ connections.add(new RosConnection(pubInfo, subInfo));
+ }
+
+ return connections;
+ }
+
+ @Override
+ public @Nullable Collection<RosConnection> getConnections() {
+ return Collections.unmodifiableCollection(fConnections);
+ }
+
+ @Override
+ public List<RosConnection> getNodeConnections(String nodeName) {
+ return fConnections.stream().filter(c -> c.getPub().getNodeName().equals(nodeName) || c.getSub().getNodeName().equals(nodeName)).collect(Collectors.toList());
+ }
+
+ @Override
+ public @Nullable RosConnection getConnection(ConnectionEndpoint endpoint, String topicName) {
+ List<RosConnection> connections = fConnections.stream().filter(c -> (c.getPub().getEndpoint().equals(endpoint) || c.getSub().getEndpoint().equals(endpoint)) && c.getPub().getTopicName().equals(topicName)).collect(Collectors.toList());
+ // Since a host:port & topic name combo is unique, there should be at
+ // most one match
+ if (connections.size() > 1) {
+ Activator.getInstance().logError("Got more than 1 connection matching a host:port and topic name pair!"); //$NON-NLS-1$
+ }
+ return (!connections.isEmpty()) ? connections.get(0) : null;
+ }
+
+ @Override
+ public @Nullable RosConnection getConnectionFromNodePublishingOnTopic(String pubNodeName, String topicName) {
+ List<RosConnection> pubConnections = fConnections.stream().filter(c -> c.getPub().getNodeName().equals(pubNodeName) && c.getPub().getTopicName().equals(topicName)).collect(Collectors.toList());
+ // Should only be one match
+ if (pubConnections.size() > 1) {
+ Activator.getInstance().logError("Got more than 1 connection matching a node and published topic name pair!"); //$NON-NLS-1$
+ }
+ return (!pubConnections.isEmpty()) ? pubConnections.get(0) : null;
+ }
+
+ @Override
+ public @Nullable RosConnection getConnectionBetweenEndpoints(ConnectionEndpoint source, ConnectionEndpoint destination) {
+ List<RosConnection> connections = fConnections.stream().filter(c -> (c.getPub().getEndpoint().equals(source) && c.getSub().getEndpoint().equals(destination))).collect(Collectors.toList());
+ // There should only be one or none
+ if (connections.size() > 1) {
+ Activator.getInstance().logError("Got more than 1 connection matching a source+destination endpoints pair!"); //$NON-NLS-1$
+ }
+ return (!connections.isEmpty()) ? connections.get(0) : null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("RosConnectionsModel"); //$NON-NLS-1$
+ b.append("\n"); //$NON-NLS-1$
+ for (RosConnection connection : fConnections) {
+ b.append("\t" + connection.toString()); //$NON-NLS-1$
+ }
+ return b.toString();
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/RosConnectionsModelAnalysis.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/RosConnectionsModelAnalysis.java
new file mode 100644
index 0000000..dc80eb2
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/connections/RosConnectionsModelAnalysis.java
@@ -0,0 +1,128 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.connections.RosConnectionsAnalysis;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.connections.RosConnectionsAnalysisRequirement;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.IRosModelProvider;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.RosModelUtils;
+import org.eclipse.tracecompass.incubator.internal.ros.core.trace.RosExperiment;
+import org.eclipse.tracecompass.incubator.internal.ros.core.trace.RosTrace;
+import org.eclipse.tracecompass.incubator.internal.ros.core.trace.layout.IRosEventLayout;
+import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
+import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.analysis.TmfAbstractAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.analysis.requirements.TmfAbstractAnalysisRequirement;
+import org.eclipse.tracecompass.tmf.core.exceptions.TmfAnalysisException;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
+import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * ROS Connections model analysis. Creates a {@link RosConnectionsModel} from
+ * the {@link RosConnectionsAnalysis} state system.
+ *
+ * @author Christophe Bedard
+ */
+public class RosConnectionsModelAnalysis extends TmfAbstractAnalysisModule implements IRosModelProvider<IRosConnectionsModel> {
+
+ /** The ID of this analysis module */
+ public static final @NonNull String ID = "org.eclipse.tracecompass.incubator.ros.core.analysis.model.connections"; //$NON-NLS-1$
+
+ /** The analysis requirements */
+ protected @Nullable Set<@NonNull TmfAbstractAnalysisRequirement> fAnalysisRequirements = null;
+
+ private @Nullable IProgressMonitor fMonitor;
+ private @Nullable RosConnectionsModel fModel = null;
+
+ /**
+ * Constructor
+ */
+ public RosConnectionsModelAnalysis() {
+ setId(ID);
+ }
+
+ @Override
+ public @Nullable RosConnectionsModel getModel() {
+ return fModel;
+ }
+
+ @Override
+ protected boolean executeAnalysis(@NonNull IProgressMonitor monitor) throws TmfAnalysisException {
+ fMonitor = monitor;
+ ITmfTrace trace = getTrace();
+ if (!(trace instanceof TmfExperiment)) {
+ throw new IllegalStateException();
+ }
+ // Connections
+ @Nullable ITmfStateSystem ss = RosModelUtils.getStateSystemFromAnalysis(trace, RosConnectionsAnalysis.class, RosConnectionsAnalysis.getFullAnalysisId());
+ if (ss == null) {
+ return false;
+ }
+
+ RosConnectionsModel model = new RosConnectionsModel(ss, monitor);
+ model.generateModel();
+ fModel = model;
+ return !monitor.isCanceled();
+ }
+
+ @Override
+ public @NonNull Iterable<@NonNull TmfAbstractAnalysisRequirement> getAnalysisRequirements() {
+ // Based on RosConnectionsAnalysis' requirements
+ Set<@NonNull TmfAbstractAnalysisRequirement> requirements = fAnalysisRequirements;
+ if (requirements == null) {
+ RosExperiment experiment = (RosExperiment) getTrace();
+ IRosEventLayout layout = IRosEventLayout.getDefault();
+ if (experiment != null) {
+ List<@NonNull ITmfTrace> traces = experiment.getTraces();
+ for (@NonNull ITmfTrace trace : traces) {
+ if (trace instanceof RosTrace) {
+ layout = ((RosTrace) trace).getEventLayout();
+ }
+ }
+ }
+ requirements = ImmutableSet.of(new RosConnectionsAnalysisRequirement(checkNotNull(layout)));
+ fAnalysisRequirements = requirements;
+ }
+ return requirements;
+ }
+
+ @Override
+ protected @NonNull Iterable<@NonNull IAnalysisModule> getDependentAnalyses() {
+ ITmfTrace trace = getTrace();
+ if (trace == null) {
+ return Collections.emptySet();
+ }
+ RosConnectionsAnalysis connections = TmfTraceUtils.getAnalysisModuleOfClass(trace, RosConnectionsAnalysis.class, RosConnectionsAnalysis.getFullAnalysisId());
+ if (connections == null) {
+ throw new IllegalStateException("There should be an analysis for this class"); //$NON-NLS-1$
+ }
+ return Collections.singleton(connections);
+ }
+
+ @Override
+ protected void canceling() {
+ IProgressMonitor mon = fMonitor;
+ if ((mon != null) && (!mon.isCanceled())) {
+ mon.setCanceled(true);
+ }
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messageflow/IRosMessageFlowModel.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messageflow/IRosMessageFlowModel.java
new file mode 100644
index 0000000..5fac91f
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messageflow/IRosMessageFlowModel.java
@@ -0,0 +1,38 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.IRosModel;
+
+/**
+ * Interface that represents the model for ROS message flow, which is a "graph"
+ * of the path a message goes take through ROS nodes, including
+ * publisher/subscriber queues and callbacks
+ *
+ * @author Christophe Bedard
+ */
+public interface IRosMessageFlowModel extends IRosModel {
+
+ /**
+ * @return true if the model is complete, false otherwise
+ */
+ boolean isModelDone();
+
+ /**
+ * TODO eventually support building segments going backwards
+ *
+ * @return get the first segment of the message flow, or {@code null} if it
+ * has not been generated
+ * @see
+ */
+ @Nullable
+ RosMessageFlowSegment getFirstSegment();
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messageflow/RosMessageFlowModel.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messageflow/RosMessageFlowModel.java
new file mode 100644
index 0000000..684cfe8
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messageflow/RosMessageFlowModel.java
@@ -0,0 +1,456 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.ros.core.Activator;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.AbstractRosStateProvider;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow.TargetMessageInfo;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow.TargetMessageInfo.RosQueueType;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections.RosConnectionEndpoint;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow.RosMessageFlowSegment.SegmentType;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messagestransport.IRosMessagesTransportModel;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messagestransport.RosMessageTransport;
+import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
+import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
+import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
+import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
+
+/**
+ * {@link IRosMessageFlowModel} implementation
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessageFlowModel implements IRosMessageFlowModel {
+
+ private final @NonNull TargetMessageInfo fInfo;
+ private final @NonNull ITmfStateSystem fQueuesSs;
+ private final @NonNull IRosMessagesTransportModel fMsgsTransportModel;
+
+ private RosMessageFlowSegment fInitialSegment = null;
+ private boolean fIsModelDone = false;
+
+ /**
+ * Constructor
+ *
+ * @param targetInfo
+ * the target message info
+ * @param queuesStateSystem
+ * the queues state system
+ * @param msgsTransportModel
+ * the messages transport model
+ */
+ public RosMessageFlowModel(@NonNull TargetMessageInfo targetInfo, @NonNull ITmfStateSystem queuesStateSystem, @NonNull IRosMessagesTransportModel msgsTransportModel) {
+ fInfo = targetInfo;
+ fQueuesSs = queuesStateSystem;
+ fMsgsTransportModel = msgsTransportModel;
+ }
+
+ @Override
+ public @Nullable RosMessageFlowSegment getFirstSegment() {
+ return fInitialSegment;
+ }
+
+ @Override
+ public boolean isModelDone() {
+ return fIsModelDone;
+ }
+
+ /**
+ * Contains necessary basic information to get/process the next segment.
+ * Serves as a mutable container.
+ */
+ private class NextSegmentInfo {
+
+ /** Type of the next segment */
+ private SegmentType fType = null;
+ /** Relevant quark for the next segment */
+ private Integer fQuark = null;
+ /** Relevant timestamp for the next segment */
+ private Long fTimestamp = null;
+ /** The next segment that was created */
+ private RosMessageFlowSegment fSegment = null;
+ /** The previous segment that was created */
+ private RosMessageFlowSegment fPreviousSegment = null;
+
+ public NextSegmentInfo() {
+ }
+
+ public SegmentType getType() {
+ return fType;
+ }
+
+ public void setType(SegmentType type) {
+ fType = type;
+ }
+
+ public Integer getQuark() {
+ return fQuark;
+ }
+
+ public void setQuark(int quark) {
+ fQuark = quark;
+ }
+
+ public Long getTimestamp() {
+ return fTimestamp;
+ }
+
+ public void setTimestamp(long t) {
+ fTimestamp = t;
+ }
+
+ public RosMessageFlowSegment getNextSegment() {
+ return fSegment;
+ }
+
+ public void setNextSegment(RosMessageFlowSegment segment) {
+ fPreviousSegment = fSegment;
+ fSegment = segment;
+ }
+
+ public RosMessageFlowSegment getPreviousSegment() {
+ return fPreviousSegment;
+ }
+ }
+
+ @Override
+ public void generateModel() {
+ try {
+ process();
+ fIsModelDone = true;
+ } catch (AttributeNotFoundException | StateSystemDisposedException e) {
+ Activator.getInstance().logError("Error while processing! ", e); //$NON-NLS-1$
+ }
+ }
+
+ private void process() throws AttributeNotFoundException, StateSystemDisposedException {
+ // Find type of initial segment corresponding to target message
+ SegmentType type = getSegmentTypeFromQueueType(fInfo.getQueueType());
+ // Find other necessary info about first segment
+ int queueQuark = getTargetQueueQuark();
+ int queuePos = fInfo.getQueuePosition();
+ int queuePosQuark = fQueuesSs.getQuarkRelative(queueQuark, String.valueOf(queuePos));
+ NextSegmentInfo next = new NextSegmentInfo();
+ next.setType(type);
+ next.setQuark(queuePosQuark);
+ next.setTimestamp(fInfo.getMsgTimestamp());
+
+ while (next.getType() != SegmentType.INVALID) {
+ // process
+ switch (next.getType()) {
+ case PUB_QUEUE:
+ processPubQueue(next);
+ break;
+ case SUB_QUEUE:
+ processSubQueue(next);
+ break;
+ case SUB_CALLBACK:
+ processCallback(next);
+ break;
+ case INVALID:
+ default:
+ Activator.getInstance().logError("Case " + next.getType().name() + " should be handled!"); //$NON-NLS-1$ //$NON-NLS-2$
+ break;
+ }
+
+ // Link previous to current
+ RosMessageFlowSegment nextSegment = next.getNextSegment();
+ RosMessageFlowSegment previousSegment = next.getPreviousSegment();
+ if (previousSegment == null) {
+ fInitialSegment = nextSegment;
+ } else {
+ previousSegment.addNext(nextSegment);
+ }
+ }
+ }
+
+ private void processPubQueue(NextSegmentInfo next) throws StateSystemDisposedException, AttributeNotFoundException {
+ int queuePosQuark = next.getQuark();
+ long msgStart = next.getTimestamp();
+
+ ITmfStateInterval firstState = fQueuesSs.querySingleState(msgStart, queuePosQuark);
+ ITmfStateInterval lastState = getLastStateOfMessageInQueue(firstState, queuePosQuark);
+ int queueQuark = fQueuesSs.getParentAttributeQuark(queuePosQuark);
+
+ long start = firstState.getStartTime();
+ long end = lastState.getEndTime();
+ int topicQuark = fQueuesSs.getParentAttributeQuark(fQueuesSs.getParentAttributeQuark(queuePosQuark));
+ int nodeQuark = fQueuesSs.getParentAttributeQuark(fQueuesSs.getParentAttributeQuark(topicQuark));
+ String nodeName = fQueuesSs.getAttributeName(nodeQuark);
+ String topicName = fQueuesSs.getAttributeName(topicQuark);
+ RosMessageFlowSegment segment = new RosMessageFlowSegment(start, end, next.getType(), nodeName, topicName);
+ next.setNextSegment(segment);
+
+ QueueSegmentTransition transition = getTransitionFromLastQueueState(lastState, fInfo.getQueueType(), queueQuark);
+ if (transition == null) {
+ Activator.getInstance().logError("Could not find transition!"); //$NON-NLS-1$
+ }
+
+ if (transition == null || transition == QueueSegmentTransition.DROP) {
+ next.setType(SegmentType.INVALID);
+ } else {
+ next.setType(SegmentType.SUB_QUEUE);
+ // Find first state after network
+ RosMessageTransport transport = fMsgsTransportModel.getNextMessageTransport(end, nodeName, topicName);
+ if (transport == null) {
+ Activator.getInstance().logError("Could not find next message transport!"); //$NON-NLS-1$
+ next.setType(SegmentType.INVALID);
+ } else {
+ // Find the subscriber queue from connection
+ RosConnectionEndpoint sub = transport.getConnection().getSub();
+ queueQuark = fQueuesSs.getQuarkAbsolute(sub.getNodeName(), AbstractRosStateProvider.SUBSCRIBERS_LIST, sub.getTopicName(), AbstractRosStateProvider.QUEUE);
+
+ ITmfStateInterval stateBeforeMsg = getStateBeforeMessageAddedToQueue(queueQuark, transport.getDestinationTimestamp());
+ int incomingMsgPosQuark = stateBeforeMsg.getAttribute();
+ long msgStateStartTime = stateBeforeMsg.getEndTime() + 1;
+
+ next.setQuark(incomingMsgPosQuark);
+ next.setTimestamp(msgStateStartTime);
+ }
+ }
+ }
+
+ private void processSubQueue(NextSegmentInfo next) throws AttributeNotFoundException, StateSystemDisposedException {
+ int incomingMsgPosQuark = next.getQuark();
+ long msgStateStartTime = next.getTimestamp();
+ ITmfStateInterval firstState = fQueuesSs.querySingleState(msgStateStartTime, incomingMsgPosQuark);
+ ITmfStateInterval lastState = getLastStateOfMessageInQueue(firstState, incomingMsgPosQuark);
+
+ long start = firstState.getStartTime();
+ long end = lastState.getEndTime();
+ int topicQuark = fQueuesSs.getParentAttributeQuark(fQueuesSs.getParentAttributeQuark(incomingMsgPosQuark));
+ int nodeQuark = fQueuesSs.getParentAttributeQuark(fQueuesSs.getParentAttributeQuark(topicQuark));
+ String nodeName = fQueuesSs.getAttributeName(nodeQuark);
+ String topicName = fQueuesSs.getAttributeName(topicQuark);
+ RosMessageFlowSegment segment = new RosMessageFlowSegment(start, end, next.getType(), nodeName, topicName);
+ next.setNextSegment(segment);
+
+ QueueSegmentTransition transition = getTransitionFromLastQueueState(lastState, RosQueueType.SUB, fQueuesSs.getParentAttributeQuark(incomingMsgPosQuark));
+ if (transition == null) {
+ Activator.getInstance().logError("Could not find transition!"); //$NON-NLS-1$
+ }
+
+ if (transition == null || transition == QueueSegmentTransition.DROP) {
+ next.setType(SegmentType.INVALID);
+ } else {
+ next.setType(SegmentType.SUB_CALLBACK);
+
+ // Callback state info
+ long callbackStart = lastState.getEndTime() + 1;
+ // Callbacks are not processed per-topic, but per-node/globally
+ int subsQuark = fQueuesSs.getParentAttributeQuark(topicQuark);
+ int callbackQuark = fQueuesSs.getQuarkRelative(subsQuark, AbstractRosStateProvider.CALLBACKS);
+
+ next.setQuark(callbackQuark);
+ next.setTimestamp(callbackStart);
+ }
+ }
+
+ private void processCallback(NextSegmentInfo next) throws StateSystemDisposedException {
+ long callbackStart = next.getTimestamp();
+ int callbackQuark = next.getQuark();
+ ITmfStateInterval callbackState = fQueuesSs.querySingleState(callbackStart, callbackQuark);
+ int nodeQuark = fQueuesSs.getParentAttributeQuark(fQueuesSs.getParentAttributeQuark(callbackQuark));
+
+ // Find messages added to the pub queues during the callback
+ List<ITmfStateInterval> pubMsgs = new ArrayList<>();
+ try {
+ int pubListQuark = fQueuesSs.getQuarkRelative(nodeQuark, AbstractRosStateProvider.PUBLISHERS_LIST);
+ List<@NonNull Integer> pubTopicQuarks = fQueuesSs.getSubAttributes(pubListQuark, false);
+ for (Integer pubTopicQuark : pubTopicQuarks) {
+ String topicName = fQueuesSs.getAttributeName(pubTopicQuark);
+ // Rejecting rosout for now
+ // TODO put back in and support
+ if (!topicName.contains("rosout")) { //$NON-NLS-1$
+ int queueQuark = fQueuesSs.getQuarkRelative(pubTopicQuark, AbstractRosStateProvider.QUEUE);
+ ITmfStateInterval stateBefore = getStateBeforeMessageAddedToQueue(queueQuark, callbackState.getStartTime() - 1);
+ // If there is a new state within the start/end of the
+ // callback
+ if (stateBefore.getEndTime() < callbackState.getEndTime()) {
+ long newMsgsStart = stateBefore.getEndTime() + 1;
+ ITmfStateInterval newPubMsg = fQueuesSs.querySingleState(newMsgsStart, stateBefore.getAttribute());
+ pubMsgs.add(newPubMsg);
+ }
+ }
+ }
+ } catch (AttributeNotFoundException e) {
+ // Do nothing, assume it means there are no more messages to be
+ // found
+ }
+
+ long start = callbackState.getStartTime();
+ long end;
+ String nodeName = fQueuesSs.getAttributeName(nodeQuark);
+ // No specific topic associated with a callback
+ String topicName = StringUtils.EMPTY;
+
+ if (pubMsgs.isEmpty()) {
+ // No messages found
+ // Still add a segment for time spent in callback
+ end = callbackState.getEndTime();
+ RosMessageFlowSegment segment = new RosMessageFlowSegment(start, end, next.getType(), nodeName, topicName);
+ next.setNextSegment(segment);
+ next.setType(SegmentType.INVALID);
+ } else {
+ // We do not support non-linear flow graphs, so just warn for now
+ // and take the first message
+ // TODO support more than one published message
+ if (pubMsgs.size() > 1) {
+ Activator.getInstance().logWarning("Found more than one published message during callback!"); //$NON-NLS-1$
+ }
+
+ ITmfStateInterval pubMsg = pubMsgs.get(0);
+ end = pubMsg.getStartTime() - 1;
+ RosMessageFlowSegment segment = new RosMessageFlowSegment(start, end, next.getType(), nodeName, topicName);
+ next.setNextSegment(segment);
+
+ next.setQuark(pubMsg.getAttribute());
+ next.setTimestamp(pubMsg.getStartTime());
+ next.setType(SegmentType.PUB_QUEUE);
+ }
+ }
+
+ /**
+ * Get the state before a new message is (possibly) added to a queue after a
+ * specific timestamp
+ *
+ * @param queueQuark
+ * the quark of the queue to consider
+ * @param initialTimestamp
+ * the last timestamp before the new message could be added to
+ * the queue (e.g. 1 ns before)
+ * @return the state before a new message is (possibly) added to the queue
+ * @throws StateSystemDisposedException
+ * @throws AttributeNotFoundException
+ */
+ private ITmfStateInterval getStateBeforeMessageAddedToQueue(int queueQuark, long initialTimestamp) throws StateSystemDisposedException, AttributeNotFoundException {
+ // Get the size of the queue right when the message
+ // is about to be added to it
+ ITmfStateInterval queueState = fQueuesSs.querySingleState(initialTimestamp, queueQuark);
+ int queueSize = (queueState.getValue() == null) ? 0 : queueState.getValueInt();
+ // Get the state at that moment; the state of the new message added to
+ // the queue should be right after, with the same quark/queue position
+ int incomingMsgPos = queueSize + 1;
+ int incomingMsgPosQuark = fQueuesSs.getQuarkRelative(queueQuark, String.valueOf(incomingMsgPos));
+ ITmfStateInterval stateBeforeNewMsg = fQueuesSs.querySingleState(initialTimestamp, incomingMsgPosQuark);
+ return stateBeforeNewMsg;
+ }
+
+ private static SegmentType getSegmentTypeFromQueueType(RosQueueType queueType) {
+ switch (queueType) {
+ case SUB:
+ return SegmentType.SUB_QUEUE;
+ case PUB:
+ return SegmentType.PUB_QUEUE;
+ default:
+ Activator.getInstance().logError("Case " + queueType.name() + " should be handled!"); //$NON-NLS-1$ //$NON-NLS-2$
+ return null;
+ }
+ }
+
+ /**
+ * Get the message's next transition after a queue
+ *
+ * @param lastState
+ * the last state of the message in the queue (at position 1)
+ * @return the next transition, or {@code null} if unknown
+ * @throws AttributeNotFoundException
+ * @throws StateSystemDisposedException
+ */
+ private QueueSegmentTransition getTransitionFromLastQueueState(ITmfStateInterval lastState, RosQueueType queueType, int queueQuark) throws StateSystemDisposedException {
+ // TODO check for "dropped" latched messages in pub queues!
+
+ // Check for drop
+ int topicQuark = fQueuesSs.getParentAttributeQuark(queueQuark);
+ try {
+ int dropsQuark = fQueuesSs.getQuarkRelative(topicQuark, AbstractRosStateProvider.DROPS);
+ ITmfStateInterval possibleDrop = fQueuesSs.querySingleState(lastState.getStartTime(), dropsQuark);
+ if (possibleDrop.getValue() != null) {
+ return QueueSegmentTransition.DROP;
+ }
+ } catch (AttributeNotFoundException e) {
+ // Do nothing, we know there's no drop if there is no "drops" quark
+ }
+
+ switch (queueType) {
+ case PUB:
+ return QueueSegmentTransition.NETWORK;
+ case SUB:
+ return QueueSegmentTransition.CALLBACK;
+ default:
+ Activator.getInstance().logError("Case " + queueType.name() + " should be handled!"); //$NON-NLS-1$ //$NON-NLS-2$
+ return null;
+ }
+ }
+
+ /**
+ * Get the last state of a given message in a queue
+ *
+ * @param message
+ * one of the states of the message
+ * @param queuePosQuark
+ * the quark corresponding to the message state
+ * @return the last state of this message in the queue
+ * @throws AttributeNotFoundException
+ * @throws StateSystemDisposedException
+ */
+ private ITmfStateInterval getLastStateOfMessageInQueue(ITmfStateInterval message, int queuePosQuark) throws AttributeNotFoundException, StateSystemDisposedException {
+ ITmfStateInterval msg = message;
+ int queueQuark = fQueuesSs.getParentAttributeQuark(queuePosQuark);
+ int pos = Integer.parseInt(fQueuesSs.getAttributeName(queuePosQuark));
+ long ref = msg.getValueLong();
+ while (pos > 1) {
+ pos--;
+ long nextStartTime = msg.getEndTime() + 1;
+ int nextQueuePosQuark = fQueuesSs.getQuarkRelative(queueQuark, String.valueOf(pos));
+ msg = fQueuesSs.querySingleState(nextStartTime, nextQueuePosQuark);
+ // Check continuity
+ if (msg.getValueLong() != ref) {
+ Activator.getInstance().logWarning("References do not match! previous=" + ref + " vs. now=" + msg.getValueLong()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ ref = msg.getValueLong();
+ }
+ return msg;
+ }
+
+ private int getTargetQueueQuark() throws AttributeNotFoundException {
+ return fQueuesSs.getQuarkAbsolute(fInfo.getNode(), getQueueTypeName(fInfo.getQueueType()), fInfo.getTopic(), AbstractRosStateProvider.QUEUE);
+ }
+
+ /**
+ * Possible transitions for a message after waiting in a queue
+ */
+ private enum QueueSegmentTransition {
+ /** Transition to a callback */
+ CALLBACK,
+ /** Sent through the network (message write) */
+ NETWORK,
+ /** Dropped from the queue */
+ DROP
+ }
+
+ private static @Nullable String getQueueTypeName(RosQueueType type) {
+ switch (type) {
+ case PUB:
+ return AbstractRosStateProvider.PUBLISHERS_LIST;
+ case SUB:
+ return AbstractRosStateProvider.SUBSCRIBERS_LIST;
+ default:
+ Activator.getInstance().logError("Case " + type.name() + " should be handled!"); //$NON-NLS-1$ //$NON-NLS-2$
+ return null;
+ }
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messageflow/RosMessageFlowSegment.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messageflow/RosMessageFlowSegment.java
new file mode 100644
index 0000000..b86af16
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messageflow/RosMessageFlowSegment.java
@@ -0,0 +1,194 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Represents a single message flow segment, with a parent/children (or not), a
+ * type, and some other information
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessageFlowSegment {
+
+ private final long fStartTime;
+ private final long fEndTime;
+ private final SegmentType fType;
+ private final String fNodeName;
+ private final String fTopicName;
+
+ private @Nullable RosMessageFlowSegment fPrevious = null;
+ private final Collection<RosMessageFlowSegment> fNext = new ArrayList<>();
+
+ /**
+ * {@link RosMessageFlowSegment} type
+ *
+ * @author Christophe Bedard
+ */
+ public enum SegmentType {
+ /** Subscriber queue */
+ SUB_QUEUE,
+ /** Subscriber callback */
+ SUB_CALLBACK,
+ /** Publisher queue */
+ PUB_QUEUE,
+ /** Invalid/nothing */
+ INVALID
+ }
+
+ /**
+ * Constructor
+ *
+ * @param startTime
+ * the start timestamp of the segment
+ * @param endTime
+ * the end timestamp of the segment
+ * @param type
+ * the segment type
+ * @param nodeName
+ * the name of the node associated with the segment
+ * @param topicName
+ * the topic associated with the segment
+ */
+ public RosMessageFlowSegment(long startTime, long endTime, SegmentType type, String nodeName, String topicName) {
+ this(startTime, endTime, type, nodeName, topicName, null);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param start
+ * the start timestamp of the segment
+ * @param end
+ * the end timestamp of the segment
+ * @param type
+ * the segment type
+ * @param nodeName
+ * the name of the node associated with the segment
+ * @param topicName
+ * the topic associated with the segment
+ * @param previous
+ * the previous segment
+ */
+ public RosMessageFlowSegment(long start, long end, SegmentType type, String nodeName, String topicName, RosMessageFlowSegment previous) {
+ fStartTime = start;
+ fEndTime = end;
+ fType = type;
+ fNodeName = nodeName;
+ fTopicName = topicName;
+ fPrevious = previous;
+ }
+
+ /**
+ * @param previous
+ * the previous segment
+ */
+ public void setPrevious(RosMessageFlowSegment previous) {
+ fPrevious = previous;
+ }
+
+ /**
+ * @param next
+ * the segment to add to the collection of next segments
+ */
+ public void addNext(RosMessageFlowSegment next) {
+ fNext.add(next);
+ }
+
+ /**
+ * @return the previous segment, or {@code null} if this is the first
+ * segment
+ */
+ public @Nullable RosMessageFlowSegment getPrevious() {
+ return fPrevious;
+ }
+
+ /**
+ * @return true if there is a previous segment, false otherwise
+ */
+ public boolean hasPrevious() {
+ return fPrevious != null;
+ }
+
+ /**
+ * @return the next segment(s), or empty if it's the last one
+ */
+ public Collection<RosMessageFlowSegment> getNext() {
+ return fNext;
+ }
+
+ /**
+ * @return true if there is a next segment, false otherwise
+ */
+ public boolean hasNext() {
+ return !fNext.isEmpty();
+ }
+
+ /**
+ * @return the start timestamp of the segment
+ */
+ public long getStartTime() {
+ return fStartTime;
+ }
+
+ /**
+ * @return the end timestamp of the segment
+ */
+ public long getEndTime() {
+ return fEndTime;
+ }
+
+ /**
+ * @return the segment type
+ */
+ public SegmentType getType() {
+ return fType;
+ }
+
+ /**
+ * @return the name of the node associated with the segment
+ */
+ public String getNodeName() {
+ return fNodeName;
+ }
+
+ /**
+ * @return the topic associated with the segment
+ */
+ public String getTopicName() {
+ return fTopicName;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fEndTime, fNext, fNodeName, fPrevious, fStartTime, fTopicName, fType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof RosMessageFlowSegment)) {
+ return false;
+ }
+ RosMessageFlowSegment other = (RosMessageFlowSegment) obj;
+ return fEndTime == other.fEndTime && Objects.equals(fNext, other.fNext) && Objects.equals(fNodeName, other.fNodeName) && Objects.equals(fPrevious, other.fPrevious) && fStartTime == other.fStartTime
+ && Objects.equals(fTopicName, other.fTopicName) && fType == other.fType;
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/IRosMessagesTransportModel.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/IRosMessagesTransportModel.java
new file mode 100644
index 0000000..9cac7c0
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/IRosMessagesTransportModel.java
@@ -0,0 +1,47 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messagestransport;
+
+import java.util.Collection;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.IRosModel;
+
+/**
+ * Interface that represents the model for ROS messages transport, with info on
+ * message exchanges between nodes
+ *
+ * @author Christophe Bedard
+ */
+public interface IRosMessagesTransportModel extends IRosModel {
+
+ /**
+ * Get the next message transport corresponding to a node publishing on a
+ * topic after a timestamp
+ *
+ * @param timestamp
+ * the timestamp after which to find the transport
+ * @param node
+ * the node name (publisher)
+ * @param topicName
+ * the topic name
+ * @return the next message transport, or {@code null} if it doesn't exist
+ */
+ @Nullable
+ RosMessageTransport getNextMessageTransport(long timestamp, String node, String topicName);
+
+ /**
+ * @return an unmodifiable view of the {@link RosMessageTransport} instances
+ * the model has, or {@code null} if the model has not been
+ * generated yet
+ */
+ @Nullable
+ Collection<RosMessageTransport> getMessageTransports();
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/NetworkConnection.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/NetworkConnection.java
new file mode 100644
index 0000000..76f3682
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/NetworkConnection.java
@@ -0,0 +1,74 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messagestransport;
+
+import java.util.Objects;
+
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections.ConnectionEndpoint;
+
+/**
+ * Represents a network connection, with source and destination
+ * {@link ConnectionEndpoint}s
+ *
+ * @author Christophe Bedard
+ */
+public class NetworkConnection {
+
+ private final ConnectionEndpoint fSourceEndpoint;
+ private final ConnectionEndpoint fDestinationEndpoint;
+
+ /**
+ * Constructor
+ *
+ * @param source
+ * the source endpoint
+ * @param destination
+ * the destination endpoint
+ */
+ public NetworkConnection(ConnectionEndpoint source, ConnectionEndpoint destination) {
+ fSourceEndpoint = source;
+ fDestinationEndpoint = destination;
+ }
+
+ /**
+ * @return the source endpoint
+ */
+ public ConnectionEndpoint getSourceEndpoint() {
+ return fSourceEndpoint;
+ }
+
+ /**
+ * @return the destination endpoint
+ */
+ public ConnectionEndpoint getDestinationEndpoint() {
+ return fDestinationEndpoint;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fDestinationEndpoint, fSourceEndpoint);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof NetworkConnection)) {
+ return false;
+ }
+ NetworkConnection other = (NetworkConnection) obj;
+ return Objects.equals(fDestinationEndpoint, other.fDestinationEndpoint)
+ && Objects.equals(fSourceEndpoint, other.fSourceEndpoint);
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessageTransport.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessageTransport.java
new file mode 100644
index 0000000..d60236f
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessageTransport.java
@@ -0,0 +1,84 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messagestransport;
+
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections.RosConnection;
+
+/**
+ * Contains information about a specific instance of a message that was
+ * transported between two nodes
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessageTransport {
+
+ private final RosConnection fConnection;
+ private final long fSourceTimestamp;
+ private final long fDestinationTimestamp;
+ // TODO remove, debug
+ private final RosMessagesTransportEventKey fKey;
+
+ /**
+ * Constructor
+ *
+ * @param connection
+ * the {@link RosConnection} responsible for this message
+ * transport
+ * @param sourceTimestamp
+ * the timestamp of the network transport source event (e.g.
+ * net_dev_queue)
+ * @param destinationTimestamp
+ * the timestamp of the network transport destination event (e.g.
+ * netif_receive_skb)
+ */
+ public RosMessageTransport(RosConnection connection, long sourceTimestamp, long destinationTimestamp, RosMessagesTransportEventKey key) {
+ fConnection = connection;
+ fSourceTimestamp = sourceTimestamp;
+ fDestinationTimestamp = destinationTimestamp;
+ fKey = key;
+ }
+
+ /**
+ * @return the {@link RosConnection} responsible for this message transport
+ */
+ public RosConnection getConnection() {
+ return fConnection;
+ }
+
+ /**
+ * @return the timestamp of the network transport source (e.g.
+ * net_dev_queue)
+ */
+ public long getSourceTimestamp() {
+ return fSourceTimestamp;
+ }
+
+ /**
+ * @return the timestamp of the network transport destination (e.g.
+ * netif_receive_skb)
+ */
+ public long getDestinationTimestamp() {
+ return fDestinationTimestamp;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("RosMessageTransport"); //$NON-NLS-1$
+ b.append("\n"); //$NON-NLS-1$
+ b.append("\t" + fConnection.toString()); //$NON-NLS-1$
+ b.append("\tSource : " + String.valueOf(fSourceTimestamp)); //$NON-NLS-1$
+ b.append("\n"); //$NON-NLS-1$
+ b.append("\tDestination: " + String.valueOf(fDestinationTimestamp)); //$NON-NLS-1$
+ b.append("\n"); //$NON-NLS-1$
+ b.append("\tkey: " + fKey.toString()); //$NON-NLS-1$
+ return b.toString();
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportEventKey.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportEventKey.java
new file mode 100644
index 0000000..49049ea
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportEventKey.java
@@ -0,0 +1,89 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messagestransport;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.event.matching.TcpEventKey;
+
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+
+/**
+ * Event matching key based on {@link TcpEventKey} with additional information
+ * on the connection information
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessagesTransportEventKey extends TcpEventKey {
+
+ private static final HashFunction HF = checkNotNull(Hashing.goodFastHash(32));
+
+ private final NetworkConnection fConnectionInfo;
+ private final long fFlags;
+
+ /**
+ * Constructor
+ *
+ * @param sequence
+ * The sequence number of the TCP packet
+ * @param ack
+ * The acknowledgement number of the TCP packet
+ * @param flags
+ * The 16 bits following the acknowledgment: data offset,
+ * reserved and flags)
+ * @param connectionInfo
+ * the network connection information
+ */
+ public RosMessagesTransportEventKey(long sequence, long ack, long flags, NetworkConnection connectionInfo) {
+ super(sequence, ack, flags);
+ fConnectionInfo = connectionInfo;
+ // Needed for match confirmation
+ fFlags = flags;
+ }
+
+ /**
+ * @return the flags associated with this packet event
+ */
+ public long getFlags() {
+ return fFlags;
+ }
+
+ /**
+ * @return the network connection information
+ */
+ public NetworkConnection getNetworkConnectionInfo() {
+ return fConnectionInfo;
+ }
+
+ @Override
+ public int hashCode() {
+ return HF.newHasher().putInt(super.hashCode()).putInt(fConnectionInfo.hashCode()).hash().asInt();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o instanceof RosMessagesTransportEventKey) {
+ RosMessagesTransportEventKey key = (RosMessagesTransportEventKey) o;
+ return super.equals(o) && key.fConnectionInfo.equals(fConnectionInfo);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("RosMessagesTransportEventKey:\n"); //$NON-NLS-1$
+ b.append("\t\tflags: " + fFlags); //$NON-NLS-1$
+ b.append("\n"); //$NON-NLS-1$
+ return b.toString();
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportEventMatching.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportEventMatching.java
new file mode 100644
index 0000000..a04000f
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportEventMatching.java
@@ -0,0 +1,109 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messagestransport;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.tracecompass.analysis.os.linux.core.trace.IKernelAnalysisEventLayout;
+import org.eclipse.tracecompass.analysis.os.linux.core.trace.IKernelTrace;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections.ConnectionEndpoint;
+import org.eclipse.tracecompass.internal.lttng2.kernel.core.TcpEventStrings;
+import org.eclipse.tracecompass.internal.lttng2.kernel.core.event.matching.TcpLttngEventMatching;
+import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
+import org.eclipse.tracecompass.tmf.core.event.TmfEventField;
+import org.eclipse.tracecompass.tmf.core.event.matching.IEventMatchingKey;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTraceWithPreDefinedEvents;
+import org.eclipse.tracecompass.tmf.core.trace.TmfEventTypeCollectionHelper;
+import org.eclipse.tracecompass.tmf.ctf.core.event.CtfTmfEventField;
+
+/**
+ * Class to match TCP events with useful ROS connection context
+ *
+ * FIXME code duplication with TcpLttngEventMatching
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessagesTransportEventMatching extends TcpLttngEventMatching {
+
+ // FIXME get base path from layout instead
+ private static final @NonNull String[] FIELD_PATH_TO_SADDR = { "network_header", CtfTmfEventField.FIELD_VARIANT_SELECTED, TcpEventStrings.SADDR }; //$NON-NLS-1$
+ private static final @NonNull String[] FIELD_PATH_TO_DADDR = { "network_header", CtfTmfEventField.FIELD_VARIANT_SELECTED, TcpEventStrings.DADDR }; //$NON-NLS-1$
+ private static final @NonNull String[] FIELD_PATH_TO_SOURCE_PORT = { "network_header", CtfTmfEventField.FIELD_VARIANT_SELECTED, "transport_header", "tcp", "source_port" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ private static final @NonNull String[] FIELD_PATH_TO_DEST_PORT = { "network_header", CtfTmfEventField.FIELD_VARIANT_SELECTED, "transport_header", "tcp", "dest_port" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+
+ private static final Map<IKernelAnalysisEventLayout, Set<String>> REQUIRED_EVENTS = new HashMap<>();
+
+ /** Use a weak hash map so that traces can be garbage collected */
+ private static final Map<ITmfTrace, IKernelAnalysisEventLayout> TRACE_LAYOUTS = new WeakHashMap<>();
+
+ @Override
+ public boolean canMatchTrace(ITmfTrace trace) {
+ // Get the events that this trace needs to have
+ if (!(trace instanceof IKernelTrace)) {
+ // Not a kernel trace, we cannot know what events to use, return
+ // false
+ return false;
+ }
+ IKernelAnalysisEventLayout layout = ((IKernelTrace) trace).getKernelEventLayout();
+ TRACE_LAYOUTS.put(trace, layout);
+
+ Set<String> events = REQUIRED_EVENTS.get(layout);
+ if (events == null) {
+ events = new HashSet<>();
+ events.addAll(layout.eventsNetworkSend());
+ events.addAll(layout.eventsNetworkReceive());
+ REQUIRED_EVENTS.put(layout, events);
+ }
+
+ if (!(trace instanceof ITmfTraceWithPreDefinedEvents)) {
+ // No predefined events, suppose events are present
+ return true;
+ }
+ ITmfTraceWithPreDefinedEvents ktrace = (ITmfTraceWithPreDefinedEvents) trace;
+
+ Set<String> traceEvents = TmfEventTypeCollectionHelper.getEventNames(ktrace.getContainedEventTypes());
+ traceEvents.retainAll(events);
+ return !traceEvents.isEmpty();
+ }
+
+ @Override
+ public IEventMatchingKey getEventKey(ITmfEvent event) {
+ IKernelAnalysisEventLayout layout = TRACE_LAYOUTS.get(event.getTrace());
+ if (layout == null) {
+ return null;
+ }
+
+ TmfEventField content = (TmfEventField) event.getContent();
+
+ Long sequence = content.getFieldValue(Long.class, layout.fieldPathTcpSeq());
+ Long ack = content.getFieldValue(Long.class, layout.fieldPathTcpAckSeq());
+ Long flags = content.getFieldValue(Long.class, layout.fieldPathTcpFlags());
+
+ long[] saddr = content.getFieldValue(long[].class, FIELD_PATH_TO_SADDR);
+ Long sport = content.getFieldValue(Long.class, FIELD_PATH_TO_SOURCE_PORT);
+ long[] daddr = content.getFieldValue(long[].class, FIELD_PATH_TO_DADDR);
+ Long dport = content.getFieldValue(Long.class, FIELD_PATH_TO_DEST_PORT);
+
+ if (sequence == null || ack == null || flags == null || saddr == null || sport == null || daddr == null || dport == null) {
+ return null;
+ }
+
+ ConnectionEndpoint sourceEndpoint = new ConnectionEndpoint(saddr, sport);
+ ConnectionEndpoint destinationEndpoint = new ConnectionEndpoint(daddr, dport);
+ NetworkConnection connection = new NetworkConnection(sourceEndpoint, destinationEndpoint);
+ return new RosMessagesTransportEventKey(sequence, ack, flags, connection);
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportEventMatchingProcessing.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportEventMatchingProcessing.java
new file mode 100644
index 0000000..98e15b4
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportEventMatchingProcessing.java
@@ -0,0 +1,123 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messagestransport;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.ros.core.Activator;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections.IRosConnectionsModel;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections.RosConnection;
+import org.eclipse.tracecompass.tmf.core.event.matching.IEventMatchingKey;
+import org.eclipse.tracecompass.tmf.core.event.matching.IMatchProcessingUnit;
+import org.eclipse.tracecompass.tmf.core.event.matching.TmfEventDependency;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+
+/**
+ * Match processing unit for ROS messages transport. Validates matches according
+ * to a {@link IRosConnectionsModel} and processes the information to eventually
+ * provide it to the {@link RosMessagesTransportModel}
+ *
+ * FIXME figure out why it sometimes does not work
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessagesTransportEventMatchingProcessing implements IMatchProcessingUnit {
+
+ private final @NonNull IRosConnectionsModel fConnectionsModel;
+ private int fCount = 0;
+ private final Collection<RosMessageTransport> fMatches = new ArrayList<>();
+
+ /**
+ * Constructor
+ *
+ * @param connectionsModel
+ * the connections model, to validate match
+ */
+ public RosMessagesTransportEventMatchingProcessing(@NonNull IRosConnectionsModel connectionsModel) {
+ fConnectionsModel = connectionsModel;
+ }
+
+ /**
+ * @return the matches
+ */
+ public Collection<RosMessageTransport> getMatches() {
+ return fMatches;
+ }
+
+ @Override
+ public void init(@NonNull Collection<@NonNull ITmfTrace> fTraces) {
+ // Nothing to do
+ }
+
+ @Override
+ public void addMatch(@NonNull TmfEventDependency match) {
+ // Not used, since we need the event key
+ Activator.getInstance().logError("Event key is needed to process a match!"); //$NON-NLS-1$
+ }
+
+ @Override
+ public void addMatch(@NonNull IEventMatchingKey eventKey, @NonNull TmfEventDependency match) {
+ if (!(eventKey instanceof RosMessagesTransportEventKey)) {
+ return;
+ }
+
+ RosMessagesTransportEventKey key = (RosMessagesTransportEventKey) eventKey;
+ // Might not be an actual ROS message
+ if (!isValidMessageMatch(key)) {
+ return;
+ }
+
+ // Check that the connection is in the RosConnectionsModel; ignore other
+ // network traffic
+ @Nullable RosConnection rosConnection = getRosConnection(key);
+ if (rosConnection == null) {
+ return;
+ }
+
+ // Process and add to the matches with important info
+ fMatches.add(new RosMessageTransport(
+ rosConnection,
+ match.getSource().getTimestamp().toNanos(),
+ match.getDestination().getTimestamp().toNanos(),
+ key));
+ fCount++;
+ }
+
+ /**
+ * Figure out if the match corresponds to an actual message transmission
+ *
+ * @param key
+ * the event key
+ * @return true if it corresponds to a message, false otherwise
+ */
+ private static boolean isValidMessageMatch(@NonNull RosMessagesTransportEventKey key) {
+ // Filter out SYN (2) and FIN (1)
+ return !((key.getFlags() & 2) != 0 || (key.getFlags() & 1) != 0);
+ }
+
+ @Override
+ public void matchingEnded() {
+ // Nothing to do
+ }
+
+ @Override
+ public int countMatches() {
+ return fCount;
+ }
+
+ private @Nullable RosConnection getRosConnection(RosMessagesTransportEventKey key) {
+ return fConnectionsModel.getConnectionBetweenEndpoints(
+ key.getNetworkConnectionInfo().getSourceEndpoint(),
+ key.getNetworkConnectionInfo().getDestinationEndpoint());
+ }
+}
\ No newline at end of file
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportModel.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportModel.java
new file mode 100644
index 0000000..b8739f7
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportModel.java
@@ -0,0 +1,104 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messagestransport;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.ros.core.Activator;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections.IRosConnectionsModel;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections.RosConnection;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections.RosConnectionsModel;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow.IRosMessageFlowModel;
+import org.eclipse.tracecompass.tmf.core.event.matching.TmfEventMatching;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+
+/**
+ * {@link IRosMessageFlowModel} implementation. Uses network event matching
+ * along with a {@link RosConnectionsModel}.
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessagesTransportModel implements IRosMessagesTransportModel {
+
+ private static RosMessagesTransportEventMatching sfMatching = null;
+
+ private final @NonNull IRosConnectionsModel fConnectionsModel;
+ private final RosMessagesTransportEventMatchingProcessing fProcessingUnit;
+ private final TmfEventMatching fMatching;
+ private Collection<RosMessageTransport> fMessageTransports = null;
+
+ /**
+ * Constructor
+ *
+ * @param trace
+ * the trace
+ * @param connectionsModel
+ * the connections model
+ */
+ public RosMessagesTransportModel(ITmfTrace trace, @NonNull IRosConnectionsModel connectionsModel) {
+ fConnectionsModel = connectionsModel;
+ fProcessingUnit = new RosMessagesTransportEventMatchingProcessing(fConnectionsModel);
+ fMatching = new TmfEventMatching(Collections.singleton(Objects.requireNonNull(trace)), fProcessingUnit);
+ }
+
+ @Override
+ public void generateModel() {
+ // TODO move to somewhere more appropriate?
+ if (sfMatching == null) {
+ sfMatching = new RosMessagesTransportEventMatching();
+ TmfEventMatching.registerMatchObject(sfMatching);
+ }
+
+ fMatching.matchEvents();
+ fMessageTransports = fProcessingUnit.getMatches();
+ }
+
+ @Override
+ public @Nullable RosMessageTransport getNextMessageTransport(long timestamp, String node, String topicName) {
+ if (fMessageTransports == null) {
+ return null;
+ }
+
+ // Get connection
+ RosConnection connection = fConnectionsModel.getConnectionFromNodePublishingOnTopic(node, topicName);
+ if (connection == null) {
+ Activator.getInstance().logError("Could not find connection!"); //$NON-NLS-1$
+ return null;
+ }
+
+ // Get closest (timewise) transport for that connection
+ long matchDiff = Long.MAX_VALUE;
+ RosMessageTransport match = null;
+ for (RosMessageTransport transport : fMessageTransports) {
+ if (transport.getConnection().equals(connection)) {
+ long diff = transport.getSourceTimestamp() - timestamp;
+ if (diff < matchDiff) {
+ matchDiff = diff;
+ match = transport;
+ }
+ }
+ }
+
+ if (match == null) {
+ Activator.getInstance().logError("Could not find closest transport!"); //$NON-NLS-1$
+ }
+
+ return match;
+ }
+
+ @Override
+ public @Nullable Collection<RosMessageTransport> getMessageTransports() {
+ return Collections.unmodifiableCollection(fMessageTransports);
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportModelAnalysis.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportModelAnalysis.java
new file mode 100644
index 0000000..6f35b0f
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/analysis/model/messagestransport/RosMessagesTransportModelAnalysis.java
@@ -0,0 +1,95 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messagestransport;
+
+import java.util.Collections;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.IRosModelProvider;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.RosModelUtils;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections.IRosConnectionsModel;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections.RosConnectionsModel;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.connections.RosConnectionsModelAnalysis;
+import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.analysis.TmfAbstractAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.exceptions.TmfAnalysisException;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
+import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment;
+
+/**
+ * ROS messages transport model analysis. Creates a
+ * {@link RosMessagesTransportModel} from a {@link RosConnectionsModel} and
+ * networking events.
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessagesTransportModelAnalysis extends TmfAbstractAnalysisModule implements IRosModelProvider<IRosMessagesTransportModel> {
+
+ /** The ID of this analysis module */
+ public static final @NonNull String ID = "org.eclipse.tracecompass.incubator.ros.core.analysis.model.messagestransport"; //$NON-NLS-1$
+
+ private @Nullable IProgressMonitor fMonitor;
+ private @Nullable RosMessagesTransportModel fModel = null;
+
+ /**
+ * Constructor
+ */
+ public RosMessagesTransportModelAnalysis() {
+ setId(ID);
+ }
+
+ @Override
+ public @Nullable RosMessagesTransportModel getModel() {
+ return fModel;
+ }
+
+ @Override
+ protected boolean executeAnalysis(@NonNull IProgressMonitor monitor) throws TmfAnalysisException {
+ ITmfTrace trace = getTrace();
+ if (!(trace instanceof TmfExperiment)) {
+ throw new IllegalStateException();
+ }
+ // Connections model
+ @Nullable
+ IRosConnectionsModel connectionsModel = RosModelUtils.getModelFromAnalysis(trace, RosConnectionsModelAnalysis.class, RosConnectionsModelAnalysis.ID);
+ if (connectionsModel == null) {
+ return false;
+ }
+
+ RosMessagesTransportModel model = new RosMessagesTransportModel(trace, connectionsModel);
+ model.generateModel();
+ fModel = model;
+ return !monitor.isCanceled();
+ }
+
+ @Override
+ protected @NonNull Iterable<@NonNull IAnalysisModule> getDependentAnalyses() {
+ ITmfTrace trace = getTrace();
+ if (trace == null) {
+ return Collections.emptySet();
+ }
+ RosConnectionsModelAnalysis connectionsModel = TmfTraceUtils.getAnalysisModuleOfClass(trace, RosConnectionsModelAnalysis.class, RosConnectionsModelAnalysis.ID);
+ if (connectionsModel == null) {
+ return Collections.emptySet();
+ }
+ return Collections.singleton(connectionsModel);
+ }
+
+ @Override
+ protected void canceling() {
+ IProgressMonitor mon = fMonitor;
+ if ((mon != null) && (!mon.isCanceled())) {
+ mon.setCanceled(true);
+ }
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/signals/RosMessageSelectedSignal.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/signals/RosMessageSelectedSignal.java
new file mode 100644
index 0000000..e9fe627
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/signals/RosMessageSelectedSignal.java
@@ -0,0 +1,43 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.signals;
+
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow.TargetMessageInfo;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignal;
+
+/**
+ * ROS message selection signal
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessageSelectedSignal extends TmfSignal {
+
+ private final TargetMessageInfo fInfo;
+
+ /**
+ * Constructor
+ *
+ * @param source
+ * the source
+ * @param info
+ * the target message info
+ */
+ public RosMessageSelectedSignal(Object source, TargetMessageInfo info) {
+ super(source, 0);
+ fInfo = info;
+ }
+
+ /**
+ * @return the target message info
+ */
+ public TargetMessageInfo getInfo() {
+ return fInfo;
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/trace/RosExperiment.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/trace/RosExperiment.java
new file mode 100644
index 0000000..579b1ce
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.core/src/org/eclipse/tracecompass/incubator/internal/ros/core/trace/RosExperiment.java
@@ -0,0 +1,45 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.core.trace;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment;
+
+/**
+ * Class for ROS experiments, which combine UST ({@link RosTrace}) and kernel
+ * traces
+ *
+ * @author Christophe Bedard
+ */
+public class RosExperiment extends TmfExperiment {
+
+ /**
+ * Default constructor. Needed by the extension point.
+ */
+ public RosExperiment() {
+ this("", Collections.EMPTY_SET); //$NON-NLS-1$
+ }
+
+ /**
+ * Constructor with traces and id
+ *
+ * @param id
+ * The ID of this experiment
+ * @param traces
+ * The set of traces that are part of this experiment
+ */
+ public RosExperiment(String id, Set<ITmfTrace> traces) {
+ super(ITmfEvent.class, id, traces.toArray(new ITmfTrace[traces.size()]), TmfExperiment.DEFAULT_INDEX_PAGE_SIZE, null);
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/icons/elcl16/follow_message.gif b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/icons/elcl16/follow_message.gif
new file mode 100644
index 0000000..c256460
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/icons/elcl16/follow_message.gif
Binary files differ
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/plugin.properties b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/plugin.properties
index 1137fd6..362b058 100644
--- a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/plugin.properties
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/plugin.properties
@@ -17,3 +17,4 @@
ros.view.tasks = Tasks (incubator)
ros.view.connections = Connections (incubator)
ros.view.timers = Timers (incubator)
+ros.view.messageflow = Message Flow (incubator)
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/plugin.xml b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/plugin.xml
index 2010441..5e713b5 100644
--- a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/plugin.xml
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/plugin.xml
@@ -48,6 +48,14 @@
name="%ros.view.timers"
restorable="true">
</view>
+ <view
+ category="org.eclipse.tracecompass.incubator.internal.ros.ui.views.category"
+ class="org.eclipse.tracecompass.incubator.internal.ros.ui.views.messageflow.RosMessageFlowView"
+ icon="icons/obj16/ros_queues.png"
+ id="org.eclipse.tracecompass.incubator.ros.ui.views.messageflow"
+ name="%ros.view.messageflow"
+ restorable="true">
+ </view>
</extension>
<extension
point="org.eclipse.linuxtools.tmf.ui.tracetypeui">
@@ -79,6 +87,19 @@
class="org.eclipse.tracecompass.incubator.internal.ros.core.analysis.timers.RosTimersAnalysis">
</analysisModuleClass>
</output>
+ <output
+ class="org.eclipse.tracecompass.tmf.ui.analysis.TmfAnalysisViewOutput"
+ id="org.eclipse.tracecompass.incubator.ros.ui.views.messageflow">
+ <analysisModuleClass
+ class="org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow.RosMessageFlowAnalysis">
+ </analysisModuleClass>
+ </output>
+ <parameterProvider
+ class="org.eclipse.tracecompass.incubator.internal.ros.ui.messageflow.RosMessageFlowParameterProvider">
+ <analysisId
+ id="org.eclipse.tracecompass.incubator.ros.core.analysis.messageflow">
+ </analysisId>
+ </parameterProvider>
</extension>
<extension
point="org.eclipse.ui.perspectives">
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/HideRosoutAction.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/actions/HideRosoutAction.java
similarity index 98%
rename from tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/HideRosoutAction.java
rename to tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/actions/HideRosoutAction.java
index 2cd9dc0..9cf54d7 100644
--- a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/HideRosoutAction.java
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/actions/HideRosoutAction.java
@@ -7,7 +7,7 @@
* http://www.eclipse.org/legal/epl-v10.html
**********************************************************************/
-package org.eclipse.tracecompass.incubator.internal.ros.ui.views;
+package org.eclipse.tracecompass.incubator.internal.ros.ui.actions;
import java.util.Objects;
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/actions/Messages.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/actions/Messages.java
new file mode 100644
index 0000000..026bed5
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/actions/Messages.java
@@ -0,0 +1,40 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.ui.actions;
+
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Messages for ROS actions
+ *
+ * @author Christophe Bedard
+ */
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = "org.eclipse.tracecompass.incubator.internal.ros.ui.actions.messages"; //$NON-NLS-1$
+
+ /** Name text for HideRosoutAction */
+ public static String HideRosoutAction_NameText;
+ /** Tooltip text for HideRosoutAction */
+ public static String HideRosoutAction_ToolTipText;
+
+ /** Name text for FollowMessageAction */
+ public static String FollowMessageAction_NameText;
+ /** Tooltip text for FollowMessageAction */
+ public static String FollowMessageAction_ToolTipText;
+
+ static {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/actions/RosFollowMessageAction.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/actions/RosFollowMessageAction.java
new file mode 100644
index 0000000..9ced1bc
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/actions/RosFollowMessageAction.java
@@ -0,0 +1,96 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.ui.actions;
+
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.action.Action;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.AbstractRosStateProvider;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow.TargetMessageInfo;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow.TargetMessageInfo.RosQueueType;
+import org.eclipse.tracecompass.incubator.internal.ros.core.signals.RosMessageSelectedSignal;
+import org.eclipse.tracecompass.incubator.internal.ros.ui.Activator;
+import org.eclipse.tracecompass.incubator.internal.ros.ui.views.queues.RosQueuesView;
+import org.eclipse.tracecompass.tmf.ui.views.TmfView;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphViewer;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
+
+/**
+ * Follow message action. Fetches and validates the selected message and
+ * broadcasts a {@link RosMessageSelectedSignal} if valid
+ *
+ * @author Christophe Bedard
+ */
+public class RosFollowMessageAction extends Action {
+
+ private static final @NonNull String IMG_UI = "icons/elcl16/follow_message.gif"; //$NON-NLS-1$
+
+ /** The corresponding view */
+ private final TmfView fView;
+ /** The time graph viewer */
+ private final TimeGraphViewer fTimeGraphViewer;
+
+ /**
+ * Constructor
+ *
+ * @param source
+ * the source view
+ * @param timeGraphViewer
+ * the timegraph viewer
+ */
+ public RosFollowMessageAction(TmfView source, TimeGraphViewer timeGraphViewer) {
+ fTimeGraphViewer = timeGraphViewer;
+ fView = source;
+
+ setText(Messages.FollowMessageAction_NameText);
+ setToolTipText(Messages.FollowMessageAction_ToolTipText);
+ setImageDescriptor(Objects.requireNonNull(Activator.getDefault()).getImageDescripterFromPath(IMG_UI));
+ }
+
+ @Override
+ public void run() {
+ long selectionBegin = fTimeGraphViewer.getSelectionBegin();
+ ITimeGraphEntry selection = fTimeGraphViewer.getSelection();
+ if (selection == null) {
+ return;
+ }
+
+ ITimeEvent msgRefEvent = RosQueuesView.getMessageReferenceEvent(selection, selectionBegin);
+ String msgRef = msgRefEvent.getLabel();
+ if (msgRef != null) {
+ RosQueueType queueType = getQueueTypeFromName(selection.getParent().getParent().getParent().getName());
+ if (queueType == null) {
+ System.err.println("Invalid queue type!"); //$NON-NLS-1$
+ return;
+ }
+
+ String node = selection.getParent().getParent().getParent().getParent().getName();
+ String topic = selection.getParent().getParent().getName();
+ int position = Integer.parseInt(selection.getName());
+ long msgBegin = msgRefEvent.getTime();
+ TargetMessageInfo target = new TargetMessageInfo(node, topic, queueType, position, msgRef, selectionBegin, msgBegin);
+ fView.broadcast(new RosMessageSelectedSignal(fView, target));
+ super.run();
+ }
+ }
+
+ private static @Nullable RosQueueType getQueueTypeFromName(String queueTypeName) {
+ if (queueTypeName.equals(AbstractRosStateProvider.PUBLISHERS_LIST)) {
+ return RosQueueType.PUB;
+ } else if (queueTypeName.equals(AbstractRosStateProvider.SUBSCRIBERS_LIST)) {
+ return RosQueueType.SUB;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/actions/messages.properties b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/actions/messages.properties
new file mode 100644
index 0000000..4c358ae
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/actions/messages.properties
@@ -0,0 +1,15 @@
+###############################################################################
+# Copyright (c) 2019 École Polytechnique de Montréal
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+###############################################################################
+
+HideRosoutAction_NameText=Hide rosout
+HideRosoutAction_ToolTipText=Hide rosout publishers (which relay a node's logs to the rosout node) and the rosout node (which may redirect logs to stdout/stderr and a file)
+
+FollowMessageAction_NameText=Follow message
+FollowMessageAction_ToolTipText=Follow the selected message
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/messageflow/RosMessageFlowParameterProvider.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/messageflow/RosMessageFlowParameterProvider.java
new file mode 100644
index 0000000..2777fcb
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/messageflow/RosMessageFlowParameterProvider.java
@@ -0,0 +1,79 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.ui.messageflow;
+
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow.RosMessageFlowAnalysis;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow.TargetMessageInfo;
+import org.eclipse.tracecompass.incubator.internal.ros.core.signals.RosMessageSelectedSignal;
+import org.eclipse.tracecompass.tmf.core.analysis.TmfAbstractAnalysisParamProvider;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+
+/**
+ * Parameter provider for the ROS Message Flow analysis. Contains the target
+ * message info.
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessageFlowParameterProvider extends TmfAbstractAnalysisParamProvider {
+
+ private static final String NAME = "ROS Message Flow parameter provider"; //$NON-NLS-1$
+
+ private TargetMessageInfo fInfo = null;
+
+ /**
+ * Constructor
+ */
+ public RosMessageFlowParameterProvider() {
+ super();
+ TmfSignalManager.register(this);
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ TmfSignalManager.deregister(this);
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public Object getParameter(String name) {
+ if (name.equals(RosMessageFlowAnalysis.TARGET_MESSAGE_INFO_KEY)) {
+ return fInfo;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean appliesToTrace(ITmfTrace trace) {
+ return true;
+ }
+
+ /**
+ * Handle {@link RosMessageSelectedSignal}
+ *
+ * @param signal
+ * the signal
+ */
+ @TmfSignalHandler
+ public void handleFollowedMessage(RosMessageSelectedSignal signal) {
+ setTargetMessageInfo(signal.getInfo());
+ }
+
+ private void setTargetMessageInfo(TargetMessageInfo info) {
+ fInfo = info;
+ notifyParameterChanged(RosMessageFlowAnalysis.TARGET_MESSAGE_INFO_KEY);
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/AbstractRosView.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/AbstractRosView.java
index 3a21b27..fe5aee6 100644
--- a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/AbstractRosView.java
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/AbstractRosView.java
@@ -16,6 +16,7 @@
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.tracecompass.incubator.internal.ros.ui.Activator;
+import org.eclipse.tracecompass.incubator.internal.ros.ui.actions.HideRosoutAction;
import org.eclipse.tracecompass.tmf.ui.views.timegraph.BaseDataProviderTimeGraphView;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphPresentationProvider;
import org.eclipse.ui.IWorkbenchActionConstants;
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/Messages.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/Messages.java
index 1d22a6c..40701fb 100644
--- a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/Messages.java
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/Messages.java
@@ -19,11 +19,6 @@
public class Messages extends NLS {
private static final String BUNDLE_NAME = "org.eclipse.tracecompass.incubator.internal.ros.ui.views.messages"; //$NON-NLS-1$
- /** Name text for HideRosoutAction */
- public static String HideRosoutAction_NameText;
- /** Tooltip text for HideRosoutAction */
- public static String HideRosoutAction_ToolTipText;
-
/** Connection channel type */
public static String AbstractRosPresentationProvider_ConnectionChannelType;
/** Connections local host */
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/connections/RosConnectionsView.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/connections/RosConnectionsView.java
index 308f61f..c29388f 100644
--- a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/connections/RosConnectionsView.java
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/connections/RosConnectionsView.java
@@ -9,6 +9,7 @@
package org.eclipse.tracecompass.incubator.internal.ros.ui.views.connections;
+import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.connections.RosConnectionsDataProvider;
import org.eclipse.tracecompass.incubator.internal.ros.ui.views.AbstractRosView;
@@ -29,6 +30,11 @@
super(ID_SUFFIX, new RosConnectionsPresentationProvider(), RosConnectionsDataProvider.getFullDataProviderId());
}
+ @Override
+ protected void fillLocalToolBar(IToolBarManager manager) {
+ super.fillLocalToolBar(manager);
+ }
+
/**
* @return the full view ID for this view
*/
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messageflow/Messages.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messageflow/Messages.java
new file mode 100644
index 0000000..829cfbf
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messageflow/Messages.java
@@ -0,0 +1,33 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.ui.views.messageflow;
+
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Messages for ROS message flow UI
+ *
+ * @author Christophe Bedard
+ */
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = "org.eclipse.tracecompass.incubator.internal.ros.ui.views.messageflow.messages"; //$NON-NLS-1$
+
+ /** Multiple states for an event */
+ public static String RosMessageFlowPresentationProvider_MultipleStates;
+
+ static {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messageflow/RosMessageFlowPresentationProvider.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messageflow/RosMessageFlowPresentationProvider.java
new file mode 100644
index 0000000..0803dd8
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messageflow/RosMessageFlowPresentationProvider.java
@@ -0,0 +1,118 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.ui.views.messageflow;
+
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.tracecompass.tmf.core.presentation.RGBAColor;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.StateItem;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphPresentationProvider;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEventStyleStrings;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeEvent;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * ROS Message Flow presentation provider
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessageFlowPresentationProvider extends TimeGraphPresentationProvider {
+
+ private static String ARROW_STATE = "arrow"; //$NON-NLS-1$
+
+ /**
+ * The enumeration of possible states for the view
+ */
+ public enum State {
+ /** Publisher queue segment */
+ PUB_QUEUE_SEGMENT(new RGB(0x3c, 0xb4, 0x4b)),
+ /** Subscriber queue segment */
+ SUB_QUEUE_SEGMENT(new RGB(0x43, 0x63, 0xd8)),
+ /** Callback segment */
+ CALLBACK_SEGMENT(new RGB(0xf5, 0x82, 0x31)),
+ /** Other segment */
+ UNKNOWN_SEGMENT(new RGB(0x40, 0x3b, 0x33)),
+ /** Network arrow (pub queue --> sub queue) */
+ NETWORK_ARROW(new RGB(0x26, 0x26, 0x26)),
+ /** Callback scheduling arrow (sub queue --> callback) */
+ CALLBACK_SCHEDULING_ARROW(new RGB(0x66, 0x66, 0x66)),
+ /** Publishing arrow (anything --> pub queue) */
+ PUBLISH_ARROW(new RGB(0xa6, 0xa6, 0xa6)),
+ /** Other arrow */
+ UNKNOWN_ARROW(new RGB(0xcc, 0xcc, 0xcc));
+
+ /** RGB color associated with a state */
+ public final RGB rgb;
+
+ private State(RGB rgb) {
+ this.rgb = rgb;
+ }
+ }
+
+ private static final StateItem[] STATE_TABLE;
+
+ static {
+ STATE_TABLE = new StateItem[State.values().length];
+ for (int i = 0; i < STATE_TABLE.length; ++i) {
+ State state = State.values()[i];
+
+ RGB stateColor = state.rgb;
+ String stateType = state.name().toLowerCase().contains(ARROW_STATE) ? ITimeEventStyleStrings.linkType() : ITimeEventStyleStrings.stateType();
+ ImmutableMap<String, Object> styleMap = ImmutableMap.of(
+ ITimeEventStyleStrings.fillStyle(), ITimeEventStyleStrings.solidColorFillStyle(),
+ ITimeEventStyleStrings.fillColor(), new RGBAColor(stateColor.red, stateColor.green, stateColor.blue).toInt(),
+ ITimeEventStyleStrings.label(), String.valueOf(state.toString()),
+ ITimeEventStyleStrings.itemTypeProperty(), stateType);
+ STATE_TABLE[i] = new StateItem(styleMap);
+ }
+ }
+
+ /**
+ * Constructor
+ */
+ public RosMessageFlowPresentationProvider() {
+ super(StringUtils.EMPTY);
+ // Nothing to do
+ }
+
+ @Override
+ public int getStateTableIndex(ITimeEvent event) {
+ if (event instanceof TimeEvent && ((TimeEvent) event).hasValue()) {
+ // Directly encoded in value
+ return ((TimeEvent) event).getValue();
+ }
+ return TRANSPARENT;
+ }
+
+ @Override
+ public StateItem[] getStateTable() {
+ return STATE_TABLE;
+ }
+
+ @Override
+ public @Nullable String getEventName(ITimeEvent event) {
+ if (event instanceof TimeEvent) {
+ TimeEvent ev = (TimeEvent) event;
+ return ev.getLabel();
+ }
+ return Messages.RosMessageFlowPresentationProvider_MultipleStates;
+ }
+
+ @Override
+ public @Nullable Map<String, String> getEventHoverToolTipInfo(ITimeEvent event) {
+ // TODO
+ return super.getEventHoverToolTipInfo(event);
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messageflow/RosMessageFlowView.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messageflow/RosMessageFlowView.java
new file mode 100644
index 0000000..0b422bf
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messageflow/RosMessageFlowView.java
@@ -0,0 +1,152 @@
+/**********************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ **********************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.ros.ui.views.messageflow;
+
+import java.util.Collections;
+import java.util.Objects;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.AbstractRosStateProvider;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow.RosMessageFlowAnalysis;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow.RosMessageFlowDataProvider;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.messageflow.RosMessageFlowSegmentEntryModel;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow.RosMessageFlowSegment;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.model.messageflow.RosMessageFlowSegment.SegmentType;
+import org.eclipse.tracecompass.incubator.internal.ros.ui.Activator;
+import org.eclipse.tracecompass.incubator.internal.ros.ui.views.AbstractRosView;
+import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphEntryModel;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
+import org.eclipse.tracecompass.tmf.core.signal.TmfStartAnalysisSignal;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry;
+import org.eclipse.ui.IWorkbenchActionConstants;
+
+/**
+ * View for {@link RosMessageFlowAnalysis}
+ *
+ * @author Christophe Bedard
+ */
+public class RosMessageFlowView extends AbstractRosView {
+
+ /** View ID suffix */
+ public static final String ID_SUFFIX = "messageflow"; //$NON-NLS-1$
+
+ private static final String[] TREE_COLUMNS = new String[] { "Node", "Type", "Topic", "Segment type" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+
+ /**
+ * Constructor
+ */
+ public RosMessageFlowView() {
+ super(ID_SUFFIX, new RosMessageFlowPresentationProvider(), RosMessageFlowDataProvider.getFullDataProviderId());
+ setTreeColumns(TREE_COLUMNS);
+ setTreeLabelProvider(new RosMessageFlowTreeLabelProvider());
+ // Enable view filter dialog
+ setFilterColumns(new String[] { StringUtils.EMPTY });
+ setFilterLabelProvider(new RosViewFilterLabelProvider());
+ }
+
+ private static class RosMessageFlowTreeLabelProvider extends TreeLabelProvider {
+ @Override
+ public String getColumnText(Object element, int columnIndex) {
+ TimeGraphEntry entry = (TimeGraphEntry) element;
+ ITimeGraphEntryModel model = entry.getModel();
+ if (model instanceof RosMessageFlowSegmentEntryModel) {
+ RosMessageFlowSegmentEntryModel segmentModel = (RosMessageFlowSegmentEntryModel) model;
+ RosMessageFlowSegment segment = segmentModel.getSegment();
+ if (columnIndex >= 0 && columnIndex < TREE_COLUMNS.length) {
+ if (columnIndex == 0) {
+ return segment.getNodeName();
+ } else if (columnIndex == 1) {
+ return getQueueTypeName(segment.getType());
+ } else if (columnIndex == 2) {
+ return segment.getTopicName();
+ } else if (columnIndex == 3) {
+ return segment.getType().name();
+ }
+ }
+ } else if (columnIndex == 0) {
+ // Experiment name
+ return entry.getName();
+ }
+
+ return StringUtils.EMPTY;
+ }
+ }
+
+ private static @Nullable String getQueueTypeName(SegmentType type) {
+ switch (type) {
+ case SUB_QUEUE: // Fallthrough
+ case SUB_CALLBACK:
+ return AbstractRosStateProvider.SUBSCRIBERS_LIST;
+ case PUB_QUEUE:
+ return AbstractRosStateProvider.PUBLISHERS_LIST;
+ case INVALID:
+ default:
+ return null;
+ }
+ }
+
+ private static class RosViewFilterLabelProvider extends TreeLabelProvider {
+ // Empty; only used to enable view filter dialog
+ }
+
+ @Override
+ protected void fillLocalToolBar(IToolBarManager manager) {
+ IDialogSettings settings = Objects.requireNonNull(Activator.getDefault()).getDialogSettings();
+ IDialogSettings section = settings.getSection(getClass().getName());
+ if (section == null) {
+ section = settings.addNewSection(getClass().getName());
+ }
+
+ IAction hideArrowsAction = getTimeGraphViewer().getHideArrowsAction(section);
+ manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, hideArrowsAction);
+
+ // add a separator to local tool bar
+ manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, new Separator());
+
+ super.fillLocalToolBar(manager);
+ }
+
+ /**
+ * Signal handler for analysis started, we need to rebuilt the entry list
+ * with updated statistics values for the current graph worker of the
+ * critical path module.
+ *
+ * @param signal
+ * The signal
+ */
+ @TmfSignalHandler
+ public void analysisStarted(TmfStartAnalysisSignal signal) {
+ IAnalysisModule analysis = signal.getAnalysisModule();
+ if (analysis instanceof RosMessageFlowAnalysis) {
+ rebuild();
+ }
+ }
+
+ @Override
+ protected @NonNull Iterable<ITmfTrace> getTracesToBuild(@Nullable ITmfTrace trace) {
+ // The view only applies to the ROS experiment itself
+ return (trace != null) ? Collections.singleton(trace) : Collections.emptyList();
+ }
+
+ /**
+ * @return the full view ID
+ */
+ public static String getFullViewId() {
+ return AbstractRosView.ID_PREFIX + ID_SUFFIX;
+ }
+}
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messageflow/messages.properties b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messageflow/messages.properties
new file mode 100644
index 0000000..379a7ac
--- /dev/null
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messageflow/messages.properties
@@ -0,0 +1,11 @@
+###############################################################################
+# Copyright (c) 2019 École Polytechnique de Montréal
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+###############################################################################
+
+RosMessageFlowPresentationProvider_MultipleStates=multiple states
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messages.properties b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messages.properties
index 91ab8ca..d1aafbc 100644
--- a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messages.properties
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/messages.properties
@@ -8,9 +8,6 @@
#
###############################################################################
-HideRosoutAction_NameText=Hide rosout
-HideRosoutAction_ToolTipText=Hide rosout publishers (which relay a node's logs to the rosout node) and the rosout node (which may redirect logs to stdout/stderr and a file)
-
AbstractRosPresentationProvider_ConnectionChannelType=Channel type
AbstractRosPresentationProvider_ConnectionHostLocal=Local
AbstractRosPresentationProvider_ConnectionHostRemote=Remote
diff --git a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/queues/RosQueuesView.java b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/queues/RosQueuesView.java
index 59e2613..b6bb8d9 100644
--- a/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/queues/RosQueuesView.java
+++ b/tracetypes/org.eclipse.tracecompass.incubator.ros.ui/src/org/eclipse/tracecompass/incubator/internal/ros/ui/views/queues/RosQueuesView.java
@@ -9,9 +9,21 @@
package org.eclipse.tracecompass.incubator.internal.ros.ui.views.queues;
+import java.util.Iterator;
+
import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.AbstractRosStateProvider;
import org.eclipse.tracecompass.incubator.internal.ros.core.analysis.queues.RosQueuesDataProvider;
+import org.eclipse.tracecompass.incubator.internal.ros.ui.actions.RosFollowMessageAction;
import org.eclipse.tracecompass.incubator.internal.ros.ui.views.AbstractRosView;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
+import org.eclipse.ui.IWorkbenchActionConstants;
/**
* View for ROS Queues
@@ -23,6 +35,8 @@
/** View ID suffix */
public static final String ID_SUFFIX = "queues"; //$NON-NLS-1$
+ private RosFollowMessageAction fFollowMessageAction = null;
+
/**
* Constructor
*/
@@ -37,6 +51,75 @@
// Empty; only used to enable view filter dialog
}
+ @Override
+ protected void fillLocalToolBar(IToolBarManager manager) {
+ fFollowMessageAction = new RosFollowMessageAction(this, getTimeGraphViewer());
+ fFollowMessageAction.setEnabled(false);
+ manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, fFollowMessageAction);
+
+ // add a separator to local tool bar
+ manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, new Separator());
+ super.fillLocalToolBar(manager);
+ }
+
+ /**
+ * {@link TmfSignalHandler} for when the selection is updated
+ *
+ * @param signal
+ * the selection range updated signal
+ */
+ @TmfSignalHandler
+ public synchronized void selection(TmfSelectionRangeUpdatedSignal signal) {
+ updateFollowMessageAction(signal.getSource());
+ }
+
+ private void updateFollowMessageAction(Object source) {
+ if (fFollowMessageAction != null) {
+ boolean isValid = false;
+ ITimeGraphEntry selection = getTimeGraphViewer().getSelection();
+ // FIXME an event is still selected in the view even if the focus is
+ // on another view, so it could still be considered valid
+ if (selection != null && source == this) {
+ ITimeEvent event = getMessageReferenceEvent(selection, getTimeGraphViewer().getSelectionBegin());
+ isValid = (event != null && event.getLabel() != null);
+ }
+ fFollowMessageAction.setEnabled(isValid);
+ }
+ }
+
+ /**
+ * Get the message event corresponding to an entry and timestamp
+ *
+ * @param selectedEntry
+ * the selected entry
+ * @param timestamp
+ * the selected timestamp
+ * @return the message event if found and valid, {@code null} otherwise
+ */
+ public static ITimeEvent getMessageReferenceEvent(ITimeGraphEntry selectedEntry, long timestamp) {
+ // It has to be a child of a queue entry
+ ITimeGraphEntry parent = selectedEntry.getParent();
+ if (parent == null || !parent.getName().equals(AbstractRosStateProvider.QUEUE)) {
+ return null;
+ }
+
+ // Find the corresponding message by comparing timestamps
+ ITimeEvent refEvent = null;
+ Iterator<@NonNull ? extends ITimeEvent> timeEventsIt = selectedEntry.getTimeEventsIterator();
+ while (timeEventsIt.hasNext()) {
+ ITimeEvent event = timeEventsIt.next();
+ long duration = event.getDuration();
+ long time = event.getTime();
+ if (duration > 0 && (time <= timestamp && timestamp < (time + duration))) {
+ refEvent = event;
+ break;
+ } else if (time > timestamp) {
+ break;
+ }
+ }
+ return refEvent;
+ }
+
/**
* @return the full view ID for this view
*/