/*******************************************************************************
* Copyright (c) 2014, 2015 Ericsson
*
* 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
*
* Contributors:
* Vincent Perot - Initial API and implementation
* Patrick Tasse - Support aspect filters
*******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.pcap.ui.stream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.tracecompass.internal.tmf.pcap.core.analysis.StreamListAnalysis;
import org.eclipse.tracecompass.internal.tmf.pcap.core.event.TmfPacketStream;
import org.eclipse.tracecompass.internal.tmf.pcap.core.event.TmfPacketStreamBuilder;
import org.eclipse.tracecompass.internal.tmf.pcap.core.event.aspect.PcapDestinationAspect;
import org.eclipse.tracecompass.internal.tmf.pcap.core.event.aspect.PcapSourceAspect;
import org.eclipse.tracecompass.internal.tmf.pcap.core.protocol.TmfPcapProtocol;
import org.eclipse.tracecompass.internal.tmf.pcap.core.signal.TmfPacketStreamSelectedSignal;
import org.eclipse.tracecompass.internal.tmf.pcap.core.trace.PcapTrace;
import org.eclipse.tracecompass.internal.tmf.pcap.ui.Activator;
import org.eclipse.tracecompass.tmf.core.event.aspect.TmfBaseAspects;
import org.eclipse.tracecompass.tmf.core.filter.model.ITmfFilterTreeNode;
import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterAndNode;
import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterAspectNode;
import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterContainsNode;
import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterNode;
import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterOrNode;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.eclipse.tracecompass.tmf.ui.project.model.TraceUtils;
import org.eclipse.tracecompass.tmf.ui.views.TmfView;
import org.eclipse.tracecompass.tmf.ui.views.filter.FilterManager;
import org.eclipse.tracecompass.tmf.ui.views.filter.FilterView;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import com.google.common.collect.Lists;
/**
* Class that represents the Stream List View. Such a view lists all the
* available streams from the current experiment. <br>
* <br>
* TODO Switch to TmfUiRefreshHandler once the behavior is fixed
*
* FIXME analysis is leaking ressource. Someone I will not name told me not to worry about it since
* AnalysisModule will not be autocloseable later.
*
* @author Vincent Perot
*/
public class StreamListView extends TmfView {
/**
* The Stream List View ID.
*/
public static final String ID = "org.eclipse.linuxtools.tmf.pcap.ui.view.stream.list"; //$NON-NLS-1$
private static final String[] COLUMN_NAMES =
{ Messages.StreamListView_ID,
Messages.StreamListView_EndpointA,
Messages.StreamListView_EndpointB,
Messages.StreamListView_TotalPackets,
Messages.StreamListView_TotalBytes,
Messages.StreamListView_PacketsAtoB,
Messages.StreamListView_BytesAtoB,
Messages.StreamListView_PacketsBtoA,
Messages.StreamListView_BytesBtoA,
Messages.StreamListView_StartTime,
Messages.StreamListView_StopTime,
Messages.StreamListView_Duration,
Messages.StreamListView_BPSAtoB,
Messages.StreamListView_BPSBtoA
};
private static final int[] COLUMN_SIZES =
{ 75,
350,
350,
110,
110,
110,
110,
110,
110,
180,
180,
110,
110,
110 };
private static final String KEY_PROTOCOL = "$protocol$"; //$NON-NLS-1$
private static final String KEY_STREAM = "$stream$"; //$NON-NLS-1$
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
private static final long WAIT_TIME = 1000;
private @Nullable CTabFolder fTabFolder;
private @Nullable Map<TmfPcapProtocol, Table> fTableMap;
private @Nullable TmfPacketStream fCurrentStream;
private @Nullable ITmfTrace fCurrentTrace;
private volatile boolean fStopThread;
/**
* Constructor of the StreamListView class.
*/
public StreamListView() {
super(ID);
}
/**
* Handler called when an trace is opened.
*
* @param signal
* Contains the information about the selection.
*/
@TmfSignalHandler
public void traceOpened(TmfTraceOpenedSignal signal) {
fCurrentTrace = signal.getTrace();
resetView();
queryAnalysis();
}
/**
* Handler called when an trace is closed. Checks if the trace is the
* current trace and update the view accordingly.
*
* @param signal
* Contains the information about the selection.
*/
@TmfSignalHandler
public void traceClosed(TmfTraceClosedSignal signal) {
if (fCurrentTrace == signal.getTrace()) {
fCurrentTrace = null;
resetView();
}
}
/**
* Handler called when an trace is selected. Checks if the trace has changed
* and requests the selected trace if it has not yet been cached.
*
* @param signal
* Contains the information about the selection.
*/
@TmfSignalHandler
public void traceSelected(TmfTraceSelectedSignal signal) {
if (fCurrentTrace != signal.getTrace()) {
fCurrentTrace = signal.getTrace();
resetView();
queryAnalysis();
}
}
private void queryAnalysis() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
ITmfTrace trace = fCurrentTrace;
if (trace == null) {
return;
}
StreamListAnalysis analysis = TmfTraceUtils.getAnalysisModuleOfClass(trace, StreamListAnalysis.class, StreamListAnalysis.ID);
if (analysis == null) {
return;
}
while (!analysis.isFinished() && !fStopThread) {
updateUI();
try {
Thread.sleep(WAIT_TIME);
} catch (InterruptedException e) {
String message = e.getMessage();
if (message == null) {
message = EMPTY_STRING;
}
Activator.logError(message, e);
return;
}
}
// Update UI one more time (daft punk)
if (!fStopThread) {
updateUI();
}
}
});
fStopThread = false;
thread.start();
}
private void resetView() {
// Stop thread if needed
fStopThread = true;
// Remove all content in tables
final Display display = Display.getDefault();
if (display == null || display.isDisposed()) {
return;
}
display.asyncExec(new Runnable() {
@Override
public void run() {
if (display.isDisposed()) {
return;
}
Map<TmfPcapProtocol, Table> tableMap = fTableMap;
if (tableMap == null) {
return;
}
for (Table table : tableMap.values()) {
if (!table.isDisposed()) {
table.removeAll();
}
}
}
});
}
private void updateUI() {
final Display display = Display.getDefault();
if (display == null || display.isDisposed()) {
return;
}
display.asyncExec(new Runnable() {
@Override
public void run() {
if (display.isDisposed()) {
return;
}
ITmfTrace trace = fCurrentTrace;
if (trace == null) {
return;
}
StreamListAnalysis analysis = TmfTraceUtils.getAnalysisModuleOfClass(trace, StreamListAnalysis.class, StreamListAnalysis.ID);
if (analysis == null) {
return;
}
Map<TmfPcapProtocol, Table> tables = fTableMap;
if (tables == null) {
return;
}
for (Entry<TmfPcapProtocol, Table> protocolEntry : tables.entrySet()) {
TmfPcapProtocol protocol = protocolEntry.getKey();
TmfPacketStreamBuilder builder = analysis.getBuilder(protocol);
Table table = protocolEntry.getValue();
if (builder != null && !(table.isDisposed())) {
for (TmfPacketStream stream : builder.getStreams()) {
TableItem item;
if (stream.getID() < table.getItemCount()) {
item = table.getItem(stream.getID());
} else {
item = new TableItem(table, SWT.NONE);
}
item.setText(0, String.valueOf(stream.getID()));
item.setText(1, stream.getFirstEndpoint().toString());
item.setText(2, stream.getSecondEndpoint().toString());
item.setText(3, String.valueOf(stream.getNbPackets()));
item.setText(4, String.valueOf(stream.getNbBytes()));
item.setText(5, String.valueOf(stream.getNbPacketsAtoB()));
item.setText(6, String.valueOf(stream.getNbBytesAtoB()));
item.setText(7, String.valueOf(stream.getNbPacketsBtoA()));
item.setText(8, String.valueOf(stream.getNbBytesBtoA()));
item.setText(9, stream.getStartTime().toString());
item.setText(10, stream.getStopTime().toString());
item.setText(11, String.format("%.3f", stream.getDuration())); //$NON-NLS-1$
item.setText(12, String.format("%.3f", stream.getBPSAtoB())); //$NON-NLS-1$
item.setText(13, String.format("%.3f", stream.getBPSBtoA())); //$NON-NLS-1$
item.setData(KEY_STREAM, stream);
}
}
}
}
});
}
@Override
public void createPartControl(@Nullable Composite parent) {
// Initialize
final Map<TmfPcapProtocol, Table> tables = new HashMap<>();
fTableMap = tables;
fCurrentTrace = TmfTraceManager.getInstance().getActiveTrace();
fCurrentStream = null;
// Add a tab folder
fTabFolder = new CTabFolder(parent, SWT.NONE);
fTabFolder.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(@Nullable SelectionEvent e) {
if (e == null) {
return;
}
TmfPcapProtocol protocol = (TmfPcapProtocol) e.item.getData(KEY_PROTOCOL);
final Table table = tables.get(protocol);
if (table != null) {
table.deselectAll();
}
fCurrentStream = null;
}
});
// Add items and tables for each protocol
for (TmfPcapProtocol protocol : TmfPcapProtocol.values()) {
if (protocol.supportsStream()) {
CTabItem item = new CTabItem(fTabFolder, SWT.NONE);
item.setText(protocol.getName());
item.setData(KEY_PROTOCOL, protocol);
Table table = new Table(fTabFolder, SWT.NONE);
table.setHeaderVisible(true);
table.setLinesVisible(true);
// Add columns to table
for (int i = 0; i < COLUMN_NAMES.length || i < COLUMN_SIZES.length; i++) {
TableColumn column = new TableColumn(table, SWT.NONE);
column.setText(COLUMN_NAMES[i]);
column.setWidth(COLUMN_SIZES[i]);
}
item.setControl(table);
table.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(@Nullable SelectionEvent e) {
if (e == null) {
return;
}
fCurrentStream = (TmfPacketStream) e.item.getData(KEY_STREAM);
}
});
tables.put(protocol, table);
// Add right click menu
Menu menu = new Menu(table);
MenuItem menuItem = new MenuItem(menu, SWT.PUSH);
menuItem.setText(Messages.StreamListView_FollowStream);
menuItem.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(@Nullable Event event) {
TmfSignal signal = new TmfPacketStreamSelectedSignal(this, 0, fCurrentStream);
TmfSignalManager.dispatchSignal(signal);
}
});
menuItem = new MenuItem(menu, SWT.PUSH);
menuItem.setText(Messages.StreamListView_Clear);
menuItem.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(@Nullable Event event) {
TmfSignal signal = new TmfPacketStreamSelectedSignal(this, 0, null);
TmfSignalManager.dispatchSignal(signal);
}
});
menuItem = new MenuItem(menu, SWT.PUSH);
menuItem.setText(Messages.StreamListView_ExtractAsFilter);
menuItem.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(@Nullable Event event) {
// Generate filter.
ITmfFilterTreeNode filter = generateFilter();
// Update view and XML
updateFilters(filter);
}
private void updateFilters(@Nullable ITmfFilterTreeNode filter) {
if (filter == null) {
return;
}
// Update XML
List<ITmfFilterTreeNode> filters = Lists.newArrayList(FilterManager.getSavedFilters());
boolean newFilter = true;
for (ITmfFilterTreeNode savedFilter : filters) {
// Use toString(explicit) equality because equals() is not implemented
if (savedFilter.toString(true).equals(filter.toString(true))) {
newFilter = false;
break;
}
}
if (newFilter) {
filters.add(filter);
FilterManager.setSavedFilters(filters.toArray(new ITmfFilterTreeNode[filters.size()]));
}
// Update Filter View
try {
final IWorkbench wb = PlatformUI.getWorkbench();
final IWorkbenchPage activePage = wb.getActiveWorkbenchWindow().getActivePage();
IViewPart view = activePage.showView(FilterView.ID);
FilterView filterView = (FilterView) view;
filterView.addFilter(filter);
} catch (final PartInitException e) {
TraceUtils.displayErrorMsg(Messages.StreamListView_ExtractAsFilter, "Error opening view " + FilterView.ID + e.getMessage()); //$NON-NLS-1$
Activator.logError("Error opening view " + FilterView.ID, e); //$NON-NLS-1$
return;
}
}
private @Nullable ITmfFilterTreeNode generateFilter() {
TmfPacketStream stream = fCurrentStream;
if (stream == null) {
return null;
}
// First stage - root
String name = Messages.StreamListView_FilterName_Stream + ' ' + stream.getProtocol().getShortName() + ' ' + stream.getFirstEndpoint()
+ " <--> " + stream.getSecondEndpoint(); //$NON-NLS-1$
TmfFilterNode root = new TmfFilterNode(name);
// Second stage - and
TmfFilterAndNode and = new TmfFilterAndNode(root);
// Third stage - protocol + or
TmfFilterContainsNode protocolFilter = new TmfFilterContainsNode(and);
protocolFilter.setEventAspect(TmfBaseAspects.getContentsAspect().forField(stream.getProtocol().getName()));
protocolFilter.setTraceTypeId(TmfFilterAspectNode.BASE_ASPECT_ID);
protocolFilter.setValue(EMPTY_STRING);
TmfFilterOrNode or = new TmfFilterOrNode(and);
// Fourth stage - and
TmfFilterAndNode andA = new TmfFilterAndNode(or);
TmfFilterAndNode andB = new TmfFilterAndNode(or);
// Fourth stage - endpoints
TmfFilterContainsNode endpointAAndA = new TmfFilterContainsNode(andA);
endpointAAndA.setEventAspect(PcapSourceAspect.INSTANCE);
endpointAAndA.setTraceTypeId(PcapTrace.TRACE_TYPE_ID);
endpointAAndA.setValue(stream.getFirstEndpoint());
TmfFilterContainsNode endpointBAndA = new TmfFilterContainsNode(andA);
endpointBAndA.setEventAspect(PcapDestinationAspect.INSTANCE);
endpointBAndA.setTraceTypeId(PcapTrace.TRACE_TYPE_ID);
endpointBAndA.setValue(stream.getSecondEndpoint());
TmfFilterContainsNode endpointAAndB = new TmfFilterContainsNode(andB);
endpointAAndB.setEventAspect(PcapSourceAspect.INSTANCE);
endpointAAndB.setTraceTypeId(PcapTrace.TRACE_TYPE_ID);
endpointAAndB.setValue(stream.getSecondEndpoint());
TmfFilterContainsNode endpointBAndB = new TmfFilterContainsNode(andB);
endpointBAndB.setEventAspect(PcapDestinationAspect.INSTANCE);
endpointBAndB.setTraceTypeId(PcapTrace.TRACE_TYPE_ID);
endpointBAndB.setValue(stream.getFirstEndpoint());
return root;
}
});
table.setMenu(menu);
}
}
// Ask the analysis for data.
queryAnalysis();
}
@Override
public void setFocus() {
CTabFolder tabFolder = fTabFolder;
if (tabFolder != null && !(tabFolder.isDisposed())) {
tabFolder.setFocus();
}
}
}