/******************************************************************************
* Copyright (c) 2015, 2016 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
*******************************************************************************/
package org.eclipse.tracecompass.analysis.timing.ui.views.segmentstore.density;
import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;
import java.text.Format;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.tracecompass.analysis.timing.core.segmentstore.IAnalysisProgressListener;
import org.eclipse.tracecompass.analysis.timing.core.segmentstore.ISegmentStoreProvider;
import org.eclipse.tracecompass.analysis.timing.ui.views.segmentstore.SubSecondTimeWithUnitFormat;
import org.eclipse.tracecompass.common.core.NonNullUtils;
import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.density.MouseDragZoomProvider;
import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.density.MouseSelectionProvider;
import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.density.SimpleTooltipProvider;
import org.eclipse.tracecompass.segmentstore.core.ISegment;
import org.eclipse.tracecompass.segmentstore.core.ISegmentStore;
import org.eclipse.tracecompass.segmentstore.core.SegmentComparators;
import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
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.signal.TmfWindowRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceContext;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.ui.viewers.TmfViewer;
import org.swtchart.Chart;
import org.swtchart.IAxis;
import org.swtchart.IBarSeries;
import org.swtchart.ISeries;
import org.swtchart.ISeries.SeriesType;
import org.swtchart.ISeriesSet;
import org.swtchart.LineStyle;
import org.swtchart.Range;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
/**
* Displays the segment store provider data in a density chart.
*
* @author Matthew Khouzam
* @author Marc-Andre Laperle
*
* @since 2.0
*/
public abstract class AbstractSegmentStoreDensityViewer extends TmfViewer {
private static final Format DENSITY_TIME_FORMATTER = new SubSecondTimeWithUnitFormat();
private static final RGB BAR_COLOR = new RGB(0x42, 0x85, 0xf4);
private final Chart fChart;
private final MouseDragZoomProvider fDragZoomProvider;
private final MouseSelectionProvider fDragProvider;
private final SimpleTooltipProvider fTooltipProvider;
private @Nullable ITmfTrace fTrace;
private @Nullable IAnalysisProgressListener fListener;
private @Nullable ISegmentStoreProvider fSegmentStoreProvider;
private TmfTimeRange fCurrentTimeRange = TmfTimeRange.NULL_RANGE;
private List<ISegmentStoreDensityViewerDataListener> fListeners;
/**
* Constructs a new density viewer.
*
* @param parent
* the parent of the viewer
*/
public AbstractSegmentStoreDensityViewer(Composite parent) {
super(parent);
fListeners = new ArrayList<>();
fChart = new Chart(parent, SWT.NONE);
fChart.getLegend().setVisible(false);
fChart.getTitle().setVisible(false);
fChart.getAxisSet().getXAxis(0).getTitle().setText(nullToEmptyString(Messages.AbstractSegmentStoreDensityViewer_TimeAxisLabel));
fChart.getAxisSet().getYAxis(0).getTitle().setText(nullToEmptyString(Messages.AbstractSegmentStoreDensityViewer_CountAxisLabel));
fChart.getAxisSet().getXAxis(0).getGrid().setStyle(LineStyle.DOT);
fChart.getAxisSet().getYAxis(0).getGrid().setStyle(LineStyle.DOT);
fDragZoomProvider = new MouseDragZoomProvider(this);
fDragZoomProvider.register();
fDragProvider = new MouseSelectionProvider(this);
fDragProvider.register();
fTooltipProvider = new SimpleTooltipProvider(this);
fTooltipProvider.register();
fChart.addDisposeListener((e) -> {
internalDispose();
});
}
/**
* Returns the segment store provider
*
* @param trace
* The trace to consider
* @return the
*/
protected @Nullable abstract ISegmentStoreProvider getSegmentStoreProvider(ITmfTrace trace);
@Nullable
private static ITmfTrace getTrace() {
return TmfTraceManager.getInstance().getActiveTrace();
}
private void updateDisplay(List<ISegment> data) {
if (data.isEmpty()) {
return;
}
IBarSeries series = (IBarSeries) fChart.getSeriesSet().createSeries(SeriesType.BAR, Messages.AbstractSegmentStoreDensityViewer_SeriesLabel);
series.setVisible(true);
series.setBarPadding(0);
series.setBarColor(new Color(Display.getDefault(), BAR_COLOR));
int barWidth = 4;
final int width = fChart.getPlotArea().getBounds().width / barWidth;
double[] xOrigSeries = new double[width];
double[] yOrigSeries = new double[width];
Arrays.fill(yOrigSeries, 1.0);
long maxLength = data.get(data.size() - 1).getLength();
double maxFactor = 1.0 / (maxLength + 1.0);
long minX = Long.MAX_VALUE;
for (ISegment segment : data) {
double xBox = segment.getLength() * maxFactor * width;
yOrigSeries[(int) xBox]++;
minX = Math.min(minX, segment.getLength());
}
double timeWidth = (double) maxLength / (double) width;
for (int i = 0; i < width; i++) {
xOrigSeries[i] = i * timeWidth;
}
double maxY = Double.MIN_VALUE;
for (int i = 0; i < width; i++) {
maxY = Math.max(maxY, yOrigSeries[i]);
}
if (minX == maxLength) {
maxLength++;
minX--;
}
series.setYSeries(yOrigSeries);
series.setXSeries(xOrigSeries);
final IAxis xAxis = fChart.getAxisSet().getXAxis(0);
/*
* adjustrange appears to bring origin back since we pad the series with
* 0s, not interesting.
*/
xAxis.adjustRange();
Range range = xAxis.getRange();
// fix for overly aggressive lower after an adjust range
range.lower = minX - range.upper + maxLength;
xAxis.setRange(range);
xAxis.getTick().setFormat(DENSITY_TIME_FORMATTER);
fChart.getAxisSet().getYAxis(0).setRange(new Range(1.0, maxY));
fChart.getAxisSet().getYAxis(0).enableLogScale(true);
fChart.redraw();
}
@Override
public Chart getControl() {
return fChart;
}
/**
* Select a range of latency durations in the viewer.
*
* @param durationRange
* a range of latency durations
*/
public void select(Range durationRange) {
computeDataAsync(fCurrentTimeRange, durationRange).thenAccept((data) -> {
for (ISegmentStoreDensityViewerDataListener listener : fListeners) {
listener.dataSelectionChanged(data);
}
});
}
/**
* Zoom to a range of latency durations in the viewer.
*
* @param durationRange
* a range of latency durations
*/
public void zoom(Range durationRange) {
computeDataAsync(fCurrentTimeRange, durationRange).thenAccept((data) -> applyData(data));
}
private CompletableFuture<@Nullable List<ISegment>> computeDataAsync(final TmfTimeRange timeRange, final Range durationRange) {
return CompletableFuture.supplyAsync(() -> computeData(timeRange, durationRange));
}
private @Nullable List<ISegment> computeData(final TmfTimeRange timeRange, final Range durationRange) {
final ISegmentStoreProvider segmentProvider = fSegmentStoreProvider;
if (segmentProvider == null) {
return null;
}
final ISegmentStore<ISegment> segStore = segmentProvider.getSegmentStore();
if (segStore == null) {
return null;
}
Iterator<ISegment> intersectingElements = segStore.getIntersectingElements(timeRange.getStartTime().getValue(), timeRange.getEndTime().getValue()).iterator();
if (durationRange.lower > Double.MIN_VALUE || durationRange.upper < Double.MAX_VALUE) {
Predicate<? super ISegment> predicate = new Predicate<ISegment>() {
@Override
public boolean apply(@Nullable ISegment input) {
return input != null && input.getLength() >= durationRange.lower && input.getLength() <= durationRange.upper;
}
};
intersectingElements = Iterators.filter(intersectingElements, predicate);
}
return Lists.newArrayList(intersectingElements);
}
private void applyData(final @Nullable List<ISegment> data) {
if (data != null) {
Collections.sort(data, SegmentComparators.INTERVAL_LENGTH_COMPARATOR);
Display.getDefault().asyncExec(() -> updateDisplay(data));
for (ISegmentStoreDensityViewerDataListener l : fListeners) {
l.dataChanged(data);
}
}
}
/**
* Sets the segment store provider
*
* @param ssp
* The segment store provider to give to this view
*
* @since 1.2
*/
@VisibleForTesting
public void setSegmentProvider(@Nullable ISegmentStoreProvider ssp) {
fSegmentStoreProvider = ssp;
}
/**
* Signal handler for handling of the window range signal.
*
* @param signal
* The {@link TmfWindowRangeUpdatedSignal}
*/
@TmfSignalHandler
public void windowRangeUpdated(@Nullable TmfWindowRangeUpdatedSignal signal) {
if (signal == null) {
return;
}
ITmfTrace trace = getTrace();
if (trace == null) {
return;
}
fSegmentStoreProvider = getSegmentStoreProvider(trace);
fCurrentTimeRange = NonNullUtils.checkNotNull(signal.getCurrentRange());
updateWithRange(fCurrentTimeRange);
}
/**
* Update the display range
*
* @param range
* the range
* @since 1.2
*/
@VisibleForTesting
public void updateWithRange(final TmfTimeRange range) {
computeDataAsync(range, new Range(Double.MIN_VALUE, Double.MAX_VALUE)).thenAccept((data) -> applyData(data));
}
@Override
public void refresh() {
fChart.redraw();
}
@Override
public void dispose() {
fChart.dispose();
}
private void internalDispose() {
if (fSegmentStoreProvider != null && fListener != null) {
fSegmentStoreProvider.removeListener(fListener);
}
fDragZoomProvider.deregister();
fTooltipProvider.deregister();
fDragProvider.deregister();
super.dispose();
}
/**
* Signal handler for handling of the trace opened signal.
*
* @param signal
* The trace opened signal {@link TmfTraceOpenedSignal}
*/
@TmfSignalHandler
public void traceOpened(TmfTraceOpenedSignal signal) {
fTrace = signal.getTrace();
loadTrace(getTrace());
}
/**
* Signal handler for handling of the trace selected signal.
*
* @param signal
* The trace selected signal {@link TmfTraceSelectedSignal}
*/
@TmfSignalHandler
public void traceSelected(TmfTraceSelectedSignal signal) {
if (fTrace != signal.getTrace()) {
fTrace = signal.getTrace();
loadTrace(getTrace());
}
}
/**
* Signal handler for handling of the trace closed signal.
*
* @param signal
* The trace closed signal {@link TmfTraceClosedSignal}
*/
@TmfSignalHandler
public void traceClosed(TmfTraceClosedSignal signal) {
if (signal.getTrace() != fTrace) {
return;
}
fTrace = null;
clearContent();
}
/**
* A Method to load a trace into the viewer.
*
* @param trace
* A trace to apply in the viewer
*/
protected void loadTrace(@Nullable ITmfTrace trace) {
clearContent();
fTrace = trace;
TmfTraceContext ctx = TmfTraceManager.getInstance().getCurrentTraceContext();
TmfTimeRange windowRange = ctx.getWindowRange();
fCurrentTimeRange = windowRange;
if (trace != null) {
fSegmentStoreProvider = getSegmentStoreProvider(trace);
final ISegmentStoreProvider provider = fSegmentStoreProvider;
if (provider != null) {
fListener = (segmentProvider, data) -> updateWithRange(windowRange);
provider.addListener(fListener);
if (provider instanceof IAnalysisModule) {
((IAnalysisModule) provider).schedule();
}
}
}
zoom(new Range(0, Long.MAX_VALUE));
}
/**
* Clears the view content.
*/
private void clearContent() {
final Chart chart = fChart;
if (!chart.isDisposed()) {
ISeriesSet set = chart.getSeriesSet();
ISeries[] series = set.getSeries();
for (int i = 0; i < series.length; i++) {
set.deleteSeries(series[i].getId());
}
for (IAxis axis : chart.getAxisSet().getAxes()) {
axis.setRange(new Range(0, 1));
}
chart.redraw();
}
}
/**
* Add a data listener.
*
* @param dataListener
* the data listener to add
*/
public void addDataListener(ISegmentStoreDensityViewerDataListener dataListener) {
fListeners.add(dataListener);
}
/**
* Remove a data listener.
*
* @param dataListener
* the data listener to remove
*/
public void removeDataListener(ISegmentStoreDensityViewerDataListener dataListener) {
fListeners.remove(dataListener);
}
}