blob: a551da7c03ccb33eab2cbef6a10ea4dde8426744 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 2018 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 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.tracecompass.analysis.timing.ui.views.segmentstore.statistics;
import java.text.Format;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
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.SegmentStoreStatisticsModel;
import org.eclipse.tracecompass.analysis.timing.core.segmentstore.statistics.AbstractSegmentStatisticsAnalysis;
import org.eclipse.tracecompass.common.core.format.SubSecondTimeWithUnitFormat;
import org.eclipse.tracecompass.internal.analysis.timing.core.segmentstore.SegmentStoreStatisticsDataProvider;
import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.statistics.Messages;
import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
import org.eclipse.tracecompass.tmf.core.analysis.TmfAbstractAnalysisModule;
import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderManager;
import org.eclipse.tracecompass.tmf.core.model.filters.FilterTimeQueryFilter;
import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataProvider;
import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeDataModel;
import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeModel;
import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse;
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.TmfGenericTreeEntry;
import org.eclipse.tracecompass.tmf.ui.viewers.tree.TmfTreeColumnData;
import org.eclipse.tracecompass.tmf.ui.viewers.tree.TmfTreeViewerEntry;
import com.google.common.collect.ImmutableList;
/**
* 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 = SubSecondTimeWithUnitFormat.getInstance();
private @Nullable TmfAbstractAnalysisModule fModule;
private MenuManager fTablePopupMenuManager;
private @Nullable String fProviderId;
/**
* Constructor
*
* @param parent
* the parent composite
* @param dataProviderId
* the data provider extension point ID.
* @since 3.0
*/
public AbstractSegmentsStatisticsViewer(Composite parent, @Nullable String dataProviderId) {
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();
}
});
fProviderId = dataProviderId;
}
/** Provides label for the Segment Store tree viewer cells */
protected static class SegmentStoreStatisticsLabelProvider extends TreeLabelProvider {
@Override
public String getColumnText(@Nullable Object element, int columnIndex) {
if (columnIndex == 0 && element instanceof TmfTreeViewerEntry) {
return String.valueOf(((TmfTreeViewerEntry) element).getName());
} else if (element instanceof TmfGenericTreeEntry) {
SegmentStoreStatisticsModel model = ((TmfGenericTreeEntry<@NonNull SegmentStoreStatisticsModel>) element).getModel();
if (model.getNbElements() == 0) {
return ""; //$NON-NLS-1$
}
if (columnIndex == 1) {
return toFormattedString(model.getMin());
} else if (columnIndex == 2) {
return String.valueOf(toFormattedString(model.getMax()));
} else if (columnIndex == 3) {
return String.valueOf(toFormattedString(model.getMean()));
} else if (columnIndex == 4) {
return String.valueOf(toFormattedString(model.getStdDev()));
} else if (columnIndex == 5) {
return String.valueOf(model.getNbElements());
} else if (columnIndex == 6) {
return String.valueOf(toFormattedString(model.getTotal()));
}
}
return ""; //$NON-NLS-1$
}
}
@Override
protected ITmfTreeColumnDataProvider getColumnDataProvider() {
return () -> ImmutableList.of(
createTmfTreeColumnData(Messages.SegmentStoreStatistics_LevelLabel, Comparator.comparing(TmfTreeViewerEntry::getName)),
createTmfTreeColumnData(Messages.SegmentStoreStatistics_Statistics_MinLabel, Comparator.comparing(keyExtractor(SegmentStoreStatisticsModel::getMin))),
createTmfTreeColumnData(Messages.SegmentStoreStatistics_MaxLabel, Comparator.comparing(keyExtractor(SegmentStoreStatisticsModel::getMax))),
createTmfTreeColumnData(Messages.SegmentStoreStatistics_AverageLabel, Comparator.comparing(keyExtractor(SegmentStoreStatisticsModel::getMean))),
createTmfTreeColumnData(Messages.SegmentStoreStatisticsViewer_StandardDeviation, Comparator.comparing(keyExtractor(SegmentStoreStatisticsModel::getStdDev))),
createTmfTreeColumnData(Messages.SegmentStoreStatisticsViewer_Count, Comparator.comparing(keyExtractor(SegmentStoreStatisticsModel::getNbElements))),
createTmfTreeColumnData(Messages.SegmentStoreStatisticsViewer_Total, Comparator.comparing(keyExtractor(SegmentStoreStatisticsModel::getTotal))),
new TmfTreeColumnData("")); //$NON-NLS-1$
}
private static TmfTreeColumnData createTmfTreeColumnData(@Nullable String name, Comparator<TmfTreeViewerEntry> comparator) {
TmfTreeColumnData column = new TmfTreeColumnData(name);
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;
}
TmfTreeViewerEntry n1 = (TmfTreeViewerEntry) e1;
TmfTreeViewerEntry n2 = (TmfTreeViewerEntry) e2;
return comparator.compare(n1, n2);
}
});
return column;
}
private static <E extends Comparable<E>, M extends SegmentStoreStatisticsModel> Function<TmfTreeViewerEntry, E>
keyExtractor(Function<M, E> modelExtractor) {
return entry -> {
if (entry instanceof TmfGenericTreeEntry) {
return modelExtractor.apply(((TmfGenericTreeEntry<M>) entry).getModel());
}
return (E) (Comparable<E>) o -> 0;
};
}
/**
* 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 TmfGenericTreeEntry) {
IAction gotoStartTime = new Action(Messages.SegmentStoreStatisticsViewer_GotoMinAction) {
@Override
public void run() {
long start;
long end;
if (element instanceof TmfGenericTreeEntry) {
SegmentStoreStatisticsModel model = ((TmfGenericTreeEntry<SegmentStoreStatisticsModel>) element).getModel();
start = model.getMinStart();
end = model.getMinEnd();
} else {
return;
}
broadcast(new TmfSelectionRangeUpdatedSignal(AbstractSegmentsStatisticsViewer.this, TmfTimestamp.fromNanos(start), TmfTimestamp.fromNanos(end), getTrace()));
updateContent(start, end, true);
}
};
IAction gotoEndTime = new Action(Messages.SegmentStoreStatisticsViewer_GotoMaxAction) {
@Override
public void run() {
long start;
long end;
if (element instanceof TmfGenericTreeEntry) {
SegmentStoreStatisticsModel model = ((TmfGenericTreeEntry<SegmentStoreStatisticsModel>) element).getModel();
start = model.getMaxStart();
end = model.getMaxEnd();
} else {
return;
}
broadcast(new TmfSelectionRangeUpdatedSignal(AbstractSegmentsStatisticsViewer.this, TmfTimestamp.fromNanos(start), TmfTimestamp.fromNanos(end), getTrace()));
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.
*/
return String.format("%s", FORMATTER.format(value)); //$NON-NLS-1$
}
@Override
protected @Nullable ITmfTreeViewerEntry updateElements(ITmfTrace trace, long start, long end, boolean isSelection) {
ITmfTreeDataProvider<SegmentStoreStatisticsModel> provider = null;
// first try to get the data provider from the data provider manager.
String providerId = fProviderId;
if (providerId != null) {
provider = DataProviderManager.getInstance().getDataProvider(trace,
fProviderId, ITmfTreeDataProvider.class);
}
// then try to get it from the legacy way
TmfAbstractAnalysisModule analysisModule = fModule;
if (provider == null && analysisModule instanceof AbstractSegmentStatisticsAnalysis && trace.equals(analysisModule.getTrace())) {
AbstractSegmentStatisticsAnalysis module = (AbstractSegmentStatisticsAnalysis) analysisModule;
provider = SegmentStoreStatisticsDataProvider.getOrCreate(trace, module);
}
if (provider == null) {
return null;
}
FilterTimeQueryFilter filter = new FilterTimeQueryFilter(start, end, 2, isSelection);
TmfModelResponse<TmfTreeModel<SegmentStoreStatisticsModel>> response = provider.fetchTree(FetchParametersUtils.filteredTimeQueryToMap(filter), null);
TmfTreeModel<SegmentStoreStatisticsModel> model = response.getModel();
if (model == null) {
return null;
}
return modelToTree(trace.getName(), model.getEntries());
}
/**
* Algorithm to convert a model (List of {@link SegmentStoreStatisticsModel}) to
* the tree.
*
* @param traceName
* trace / experiment name, we add it to help when debugging.
* @param model
* model to convert
* @return the resulting {@link TmfTreeViewerEntry}.
*/
private static @Nullable TmfTreeViewerEntry modelToTree(String traceName, List<SegmentStoreStatisticsModel> model) {
TmfTreeViewerEntry root = new TmfTreeViewerEntry(traceName);
Map<Long, TmfTreeViewerEntry> map = new HashMap<>();
map.put(-1L, root);
for (TmfTreeDataModel entry : model) {
TmfTreeViewerEntry viewerEntry;
if (entry.getParentId() != -1) {
viewerEntry = new TmfGenericTreeEntry<>(entry);
} else {
/*
* create a regular TmfTreeViewerEntry to avoid displaying statistics for trace
* level entries.
*/
viewerEntry = new TmfTreeViewerEntry(entry.getName());
}
map.put(entry.getId(), viewerEntry);
TmfTreeViewerEntry parent = map.get(entry.getParentId());
if (parent != null && !parent.getChildren().contains(viewerEntry)) {
parent.addChild(viewerEntry);
}
}
return root;
}
@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.
}
@Override
protected void setSelectionRange(long selectionBeginTime, long selectionEndTime) {
super.setSelectionRange(selectionBeginTime, selectionEndTime);
updateContent(selectionBeginTime, selectionEndTime, true);
}
/**
* Get the type label
*
* @return the label
* @since 1.2
*/
protected String getTypeLabel() {
return Objects.requireNonNull(Messages.AbstractSegmentStoreStatisticsViewer_types);
}
/**
* Get the total column label
*
* @return the totals column label
* @since 1.2
*/
protected String getTotalLabel() {
return Objects.requireNonNull(Messages.AbstractSegmentStoreStatisticsViewer_total);
}
/**
* Get the selection column label
*
* @return The selection column label
* @since 1.2
*/
protected String getSelectionLabel() {
return Objects.requireNonNull(Messages.AbstractSegmentStoreStatisticsViewer_selection);
}
/**
* Setter for the provider ID
*
* @param newProviderId
* the new provider ID to use
* @since 3.0
*/
public void setProviderId(String newProviderId) {
fProviderId = newProviderId;
}
}