/*******************************************************************************
* Copyright (c) 2015, 2017 Ericsson, É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.analysis.timing.ui.views.segmentstore.statistics;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.text.Format;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.tracecompass.analysis.timing.core.segmentstore.statistics.AbstractSegmentStatisticsAnalysis;
import org.eclipse.tracecompass.analysis.timing.core.statistics.IStatistics;
import org.eclipse.tracecompass.analysis.timing.core.statistics.Statistics;
import org.eclipse.tracecompass.analysis.timing.ui.views.segmentstore.SubSecondTimeWithUnitFormat;
import org.eclipse.tracecompass.internal.analysis.timing.ui.Activator;
import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.statistics.Messages;
import org.eclipse.tracecompass.segmentstore.core.ISegment;
import org.eclipse.tracecompass.tmf.core.analysis.TmfAbstractAnalysisModule;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfAnalysisException;
import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfWindowRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.ui.viewers.tree.AbstractTmfTreeViewer;
import org.eclipse.tracecompass.tmf.ui.viewers.tree.ITmfTreeColumnDataProvider;
import org.eclipse.tracecompass.tmf.ui.viewers.tree.ITmfTreeViewerEntry;
import org.eclipse.tracecompass.tmf.ui.viewers.tree.TmfTreeColumnData;
import org.eclipse.tracecompass.tmf.ui.viewers.tree.TmfTreeViewerEntry;
/**
* An abstract tree viewer implementation for displaying segment store
* statistics
*
* @author Bernd Hufmann
* @author Geneviève Bastien
* @since 1.3
*/
public abstract class AbstractSegmentsStatisticsViewer extends AbstractTmfTreeViewer {
private static final Format FORMATTER = new SubSecondTimeWithUnitFormat();
private @Nullable TmfAbstractAnalysisModule fModule;
private MenuManager fTablePopupMenuManager;
private static final String[] COLUMN_NAMES = new String[] {
checkNotNull(Messages.SegmentStoreStatistics_LevelLabel),
checkNotNull(Messages.SegmentStoreStatistics_Statistics_MinLabel),
checkNotNull(Messages.SegmentStoreStatistics_MaxLabel),
checkNotNull(Messages.SegmentStoreStatistics_AverageLabel),
checkNotNull(Messages.SegmentStoreStatisticsViewer_StandardDeviation),
checkNotNull(Messages.SegmentStoreStatisticsViewer_Count),
checkNotNull(Messages.SegmentStoreStatisticsViewer_Total)
};
/**
* Constructor
*
* @param parent
* the parent composite
*/
public AbstractSegmentsStatisticsViewer(Composite parent) {
super(parent, false);
setLabelProvider(new SegmentStoreStatisticsLabelProvider());
fTablePopupMenuManager = new MenuManager();
fTablePopupMenuManager.setRemoveAllWhenShown(true);
fTablePopupMenuManager.addMenuListener(manager -> {
TreeViewer viewer = getTreeViewer();
ISelection selection = viewer.getSelection();
if (selection instanceof IStructuredSelection) {
IStructuredSelection sel = (IStructuredSelection) selection;
if (manager != null) {
appendToTablePopupMenu(manager, sel);
}
}
});
Menu tablePopup = fTablePopupMenuManager.createContextMenu(getTreeViewer().getTree());
Tree tree = getTreeViewer().getTree();
tree.setMenu(tablePopup);
tree.addDisposeListener(e -> {
if (fModule != null) {
fModule.dispose();
}
});
}
/** Provides label for the Segment Store tree viewer cells */
protected static class SegmentStoreStatisticsLabelProvider extends TreeLabelProvider {
@Override
public String getColumnText(@Nullable Object element, int columnIndex) {
String value = ""; //$NON-NLS-1$
if (element instanceof HiddenTreeViewerEntry) {
if (columnIndex == 0) {
value = ((HiddenTreeViewerEntry) element).getName();
}
} else if (element instanceof SegmentStoreStatisticsEntry) {
SegmentStoreStatisticsEntry entry = (SegmentStoreStatisticsEntry) element;
if (columnIndex == 0) {
return String.valueOf(entry.getName());
}
if (entry.getEntry().getNbElements() > 0) {
if (columnIndex == 1) {
value = toFormattedString(entry.getEntry().getMin());
} else if (columnIndex == 2) {
value = String.valueOf(toFormattedString(entry.getEntry().getMax()));
} else if (columnIndex == 3) {
value = String.valueOf(toFormattedString(entry.getEntry().getMean()));
} else if (columnIndex == 4) {
value = String.valueOf(toFormattedString(entry.getEntry().getStdDev()));
} else if (columnIndex == 5) {
value = String.valueOf(entry.getEntry().getNbElements());
} else if (columnIndex == 6) {
value = String.valueOf(toFormattedString(entry.getEntry().getTotal()));
}
}
}
return checkNotNull(value);
}
}
/**
* Creates the statistics analysis module
*
* @return the statistics analysis module
*/
@Nullable
protected abstract TmfAbstractAnalysisModule createStatisticsAnalysiModule();
/**
* Gets the statistics analysis module
*
* @return the statistics analysis module
*/
@Nullable
public TmfAbstractAnalysisModule getStatisticsAnalysisModule() {
return fModule;
}
@Override
protected ITmfTreeColumnDataProvider getColumnDataProvider() {
return new ITmfTreeColumnDataProvider() {
@Override
public List<@Nullable TmfTreeColumnData> getColumnData() {
/* All columns are sortable */
List<@Nullable TmfTreeColumnData> columns = new ArrayList<>();
TmfTreeColumnData column = new TmfTreeColumnData(COLUMN_NAMES[0]);
column.setAlignment(SWT.RIGHT);
column.setComparator(new ViewerComparator() {
@Override
public int compare(@Nullable Viewer viewer, @Nullable Object e1, @Nullable Object e2) {
if ((e1 == null) || (e2 == null)) {
return 0;
}
SegmentStoreStatisticsEntry n1 = (SegmentStoreStatisticsEntry) e1;
SegmentStoreStatisticsEntry n2 = (SegmentStoreStatisticsEntry) e2;
return n1.getName().compareTo(n2.getName());
}
});
columns.add(column);
column = new TmfTreeColumnData(COLUMN_NAMES[1]);
column.setAlignment(SWT.RIGHT);
column.setComparator(new ViewerComparator() {
@Override
public int compare(@Nullable Viewer viewer, @Nullable Object e1, @Nullable Object e2) {
if ((e1 == null) || (e2 == null)) {
return 0;
}
SegmentStoreStatisticsEntry n1 = (SegmentStoreStatisticsEntry) e1;
SegmentStoreStatisticsEntry n2 = (SegmentStoreStatisticsEntry) e2;
return Long.compare(n1.getEntry().getMin(), n2.getEntry().getMin());
}
});
columns.add(column);
column = new TmfTreeColumnData(COLUMN_NAMES[2]);
column.setAlignment(SWT.RIGHT);
column.setComparator(new ViewerComparator() {
@Override
public int compare(@Nullable Viewer viewer, @Nullable Object e1, @Nullable Object e2) {
if ((e1 == null) || (e2 == null)) {
return 0;
}
SegmentStoreStatisticsEntry n1 = (SegmentStoreStatisticsEntry) e1;
SegmentStoreStatisticsEntry n2 = (SegmentStoreStatisticsEntry) e2;
return Long.compare(n1.getEntry().getMax(), n2.getEntry().getMax());
}
});
columns.add(column);
column = new TmfTreeColumnData(COLUMN_NAMES[3]);
column.setAlignment(SWT.RIGHT);
column.setComparator(new ViewerComparator() {
@Override
public int compare(@Nullable Viewer viewer, @Nullable Object e1, @Nullable Object e2) {
if ((e1 == null) || (e2 == null)) {
return 0;
}
SegmentStoreStatisticsEntry n1 = (SegmentStoreStatisticsEntry) e1;
SegmentStoreStatisticsEntry n2 = (SegmentStoreStatisticsEntry) e2;
return Double.compare(n1.getEntry().getMean(), n2.getEntry().getMean());
}
});
columns.add(column);
column = new TmfTreeColumnData(COLUMN_NAMES[4]);
column.setAlignment(SWT.RIGHT);
column.setComparator(new ViewerComparator() {
@Override
public int compare(@Nullable Viewer viewer, @Nullable Object e1, @Nullable Object e2) {
if ((e1 == null) || (e2 == null)) {
return 0;
}
SegmentStoreStatisticsEntry n1 = (SegmentStoreStatisticsEntry) e1;
SegmentStoreStatisticsEntry n2 = (SegmentStoreStatisticsEntry) e2;
return Double.compare(n1.getEntry().getStdDev(), n2.getEntry().getStdDev());
}
});
columns.add(column);
column = new TmfTreeColumnData(COLUMN_NAMES[5]);
column.setAlignment(SWT.RIGHT);
column.setComparator(new ViewerComparator() {
@Override
public int compare(@Nullable Viewer viewer, @Nullable Object e1, @Nullable Object e2) {
if ((e1 == null) || (e2 == null)) {
return 0;
}
SegmentStoreStatisticsEntry n1 = (SegmentStoreStatisticsEntry) e1;
SegmentStoreStatisticsEntry n2 = (SegmentStoreStatisticsEntry) e2;
return Long.compare(n1.getEntry().getNbElements(), n2.getEntry().getNbElements());
}
});
columns.add(column);
column = new TmfTreeColumnData(COLUMN_NAMES[6]);
column.setAlignment(SWT.RIGHT);
column.setComparator(new ViewerComparator() {
@Override
public int compare(@Nullable Viewer viewer, @Nullable Object e1, @Nullable Object e2) {
if ((e1 == null) || (e2 == null)) {
return 0;
}
SegmentStoreStatisticsEntry n1 = (SegmentStoreStatisticsEntry) e1;
SegmentStoreStatisticsEntry n2 = (SegmentStoreStatisticsEntry) e2;
return Double.compare(n1.getEntry().getTotal(), n2.getEntry().getTotal());
}
});
columns.add(column);
column = new TmfTreeColumnData(""); //$NON-NLS-1$
columns.add(column);
return columns;
}
};
}
@Override
public void initializeDataSource() {
ITmfTrace trace = getTrace();
if (trace != null) {
TmfAbstractAnalysisModule module = createStatisticsAnalysiModule();
if (module == null) {
return;
}
try {
module.setTrace(trace);
module.schedule();
if (fModule != null) {
fModule.dispose();
}
fModule = module;
} catch (TmfAnalysisException e) {
Activator.getDefault().logError("Error initializing statistics analysis module", e); //$NON-NLS-1$
}
}
}
/**
* Method to add commands to the context sensitive menu.
*
* @param manager
* the menu manager
* @param sel
* the current selection
*/
protected void appendToTablePopupMenu(IMenuManager manager, IStructuredSelection sel) {
Object element = sel.getFirstElement();
if ((element instanceof SegmentStoreStatisticsEntry) && !(element instanceof HiddenTreeViewerEntry)) {
final SegmentStoreStatisticsEntry segment = (SegmentStoreStatisticsEntry) element;
IAction gotoStartTime = new Action(Messages.SegmentStoreStatisticsViewer_GotoMinAction) {
@Override
public void run() {
ISegment minObject = segment.getEntry().getMinObject();
long start = minObject == null ? 0 : minObject.getStart();
long end = minObject == null ? 0 : minObject.getEnd();
broadcast(new TmfSelectionRangeUpdatedSignal(AbstractSegmentsStatisticsViewer.this, TmfTimestamp.fromNanos(start), TmfTimestamp.fromNanos(end)));
updateContent(start, end, true);
}
};
IAction gotoEndTime = new Action(Messages.SegmentStoreStatisticsViewer_GotoMaxAction) {
@Override
public void run() {
ISegment maxObject = segment.getEntry().getMaxObject();
long start = maxObject == null ? 0 : maxObject.getStart();
long end = maxObject == null ? 0 : maxObject.getEnd();
broadcast(new TmfSelectionRangeUpdatedSignal(AbstractSegmentsStatisticsViewer.this, TmfTimestamp.fromNanos(start), TmfTimestamp.fromNanos(end)));
updateContent(start, end, true);
}
};
manager.add(gotoStartTime);
manager.add(gotoEndTime);
}
}
/**
* Formats a double value string
*
* @param value
* a value to format
* @return formatted value
*/
protected static String toFormattedString(double value) {
// The cast to long is needed because the formatter cannot truncate the
// number.
String percentageString = String.format("%s", FORMATTER.format(value)); //$NON-NLS-1$
return percentageString;
}
/**
* Class for defining an entry in the statistics tree.
*/
protected class SegmentStoreStatisticsEntry extends TmfTreeViewerEntry {
private final IStatistics<ISegment> fEntry;
/**
* Constructor
*
* @param name
* name of entry
*
* @param entry
* segment store statistics object
*/
public SegmentStoreStatisticsEntry(String name, IStatistics<ISegment> entry) {
super(name);
fEntry = entry;
}
/**
* Gets the statistics object
*
* @return statistics object
*/
public IStatistics<ISegment> getEntry() {
return fEntry;
}
}
@Override
protected @Nullable ITmfTreeViewerEntry updateElements(long start, long end, boolean isSelection) {
TmfAbstractAnalysisModule analysisModule = getStatisticsAnalysisModule();
if (getTrace() == null || !(analysisModule instanceof AbstractSegmentStatisticsAnalysis)) {
return null;
}
AbstractSegmentStatisticsAnalysis module = (AbstractSegmentStatisticsAnalysis) analysisModule;
module.waitForCompletion();
TmfTreeViewerEntry root = new TmfTreeViewerEntry(""); //$NON-NLS-1$
List<ITmfTreeViewerEntry> entryList = root.getChildren();
if (isSelection) {
setStats(start, end, entryList, module, true, new NullProgressMonitor());
}
setStats(start, end, entryList, module, false, new NullProgressMonitor());
return root;
}
private void setStats(long start, long end, List<ITmfTreeViewerEntry> entryList, AbstractSegmentStatisticsAnalysis module, boolean isSelection, IProgressMonitor monitor) {
String label = isSelection ? getSelectionLabel() : getTotalLabel();
final IStatistics<ISegment> entry = isSelection ? module.getStatsForRange(start, end, monitor) : module.getStatsTotal();
if (entry != null) {
if (entry.getNbElements() == 0) {
return;
}
TmfTreeViewerEntry child = new SegmentStoreStatisticsEntry(checkNotNull(label), entry);
entryList.add(child);
final Map<@NonNull String, IStatistics<ISegment>> perTypeStats = isSelection ? module.getStatsPerTypeForRange(start, end, monitor) : module.getStatsPerType();
for (Entry<@NonNull String, IStatistics<ISegment>> statsEntry : perTypeStats.entrySet()) {
child.addChild(new SegmentStoreStatisticsEntry(statsEntry.getKey(), statsEntry.getValue()));
}
}
}
@Override
@TmfSignalHandler
public void windowRangeUpdated(@Nullable TmfWindowRangeUpdatedSignal signal) {
// Do nothing. We do not want to update the view and lose the selection
// if the window range is updated with current selection outside of this
// new range.
}
/**
* Get the type label
*
* @return the label
* @since 1.2
*/
protected String getTypeLabel() {
return checkNotNull(Messages.AbstractSegmentStoreStatisticsViewer_types);
}
/**
* Get the total column label
*
* @return the totals column label
* @since 1.2
*/
protected String getTotalLabel() {
return checkNotNull(Messages.AbstractSegmentStoreStatisticsViewer_total);
}
/**
* Get the selection column label
*
* @return The selection column label
* @since 1.2
*/
protected String getSelectionLabel() {
return checkNotNull(Messages.AbstractSegmentStoreStatisticsViewer_selection);
}
/**
* Class to define a level in the tree that doesn't have any values.
*/
protected class HiddenTreeViewerEntry extends SegmentStoreStatisticsEntry {
/**
* Constructor
*
* @param name
* the name of the level
*/
public HiddenTreeViewerEntry(String name) {
super(name, new Statistics<>(s -> s.getLength()));
}
}
}