blob: 2616e2fabafbbb39f51cd85145aa700756a9f114 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2015, 2018 Ericsson
*
* 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.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.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.resource.ColorRegistry;
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.common.core.NonNullUtils;
import org.eclipse.tracecompass.common.core.format.SubSecondTimeWithUnitFormat;
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.internal.analysis.timing.ui.views.segmentstore.table.SegmentStoreContentProvider.SegmentStoreWithRange;
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.presentation.IYAppearance;
import org.eclipse.tracecompass.tmf.core.presentation.IYAppearance.Type;
import org.eclipse.tracecompass.tmf.core.presentation.RotatingPaletteProvider;
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.colors.RGBAUtil;
import org.eclipse.tracecompass.tmf.ui.viewers.IImageSave;
import org.eclipse.tracecompass.tmf.ui.viewers.TmfViewer;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphColorScheme;
import org.swtchart.Chart;
import org.swtchart.IAxis;
import org.swtchart.IBarSeries;
import org.swtchart.ILegend;
import org.swtchart.ILineSeries;
import org.swtchart.ILineSeries.PlotSymbolType;
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;
/**
* Displays the segment store provider data in a density chart.
*
* @author Matthew Khouzam
* @author Marc-Andre Laperle
*
* @since 2.0
*/
@Deprecated
public abstract class AbstractSegmentStoreDensityViewer extends TmfViewer implements IImageSave {
private static final Format DENSITY_TIME_FORMATTER = SubSecondTimeWithUnitFormat.getInstance();
private static final RGB BAR_COLOR = new RGB(0x42, 0x85, 0xf4);
private static final ColorRegistry COLOR_REGISTRY = new ColorRegistry();
private static final RotatingPaletteProvider PALETTE = new RotatingPaletteProvider.Builder().setSaturation(0.73f).setBrightness(0.957f).setNbColors(60).build();
/** The color scheme for the chart */
private TimeGraphColorScheme fColorScheme = new TimeGraphColorScheme();
private final Chart fChart;
private final MouseDragZoomProvider fDragZoomProvider;
private final MouseSelectionProvider fDragProvider;
private final SimpleTooltipProvider fTooltipProvider;
private @Nullable ITmfTrace fTrace;
private Map<@NonNull String, @NonNull IAnalysisProgressListener> fProgressListeners = new HashMap<>();
private final Map<@NonNull String, @NonNull ISegmentStoreProvider> fSegmentStoreProviders = new HashMap<>();
private Range fCurrentDurationRange = new Range(AbstractSegmentStoreDensityView.DEFAULT_RANGE.getFirst(), AbstractSegmentStoreDensityView.DEFAULT_RANGE.getSecond());
private TmfTimeRange fCurrentTimeRange = TmfTimeRange.NULL_RANGE;
private final List<ISegmentStoreDensityViewerDataListener> fListeners;
private int fOverrideNbPoints;
private String fSeriesType;
private final Set<@NonNull ITmfTrace> fTraces = new HashSet<>();
/**
* 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);
Color backgroundColor = fColorScheme.getColor(TimeGraphColorScheme.TOOL_BACKGROUND);
fChart.setBackground(backgroundColor);
backgroundColor = fColorScheme.getColor(TimeGraphColorScheme.BACKGROUND);
fChart.setBackgroundInPlotArea(backgroundColor);
parent.setBackground(backgroundColor);
Color foregroundColor = fColorScheme.getColor(TimeGraphColorScheme.TOOL_FOREGROUND);
fChart.setForeground(foregroundColor);
fChart.getLegend().setVisible(false);
fChart.getTitle().setVisible(false);
IAxis xAxis = fChart.getAxisSet().getXAxis(0);
IAxis yAxis = fChart.getAxisSet().getYAxis(0);
xAxis.getTitle().setText(nullToEmptyString(Messages.AbstractSegmentStoreDensityViewer_TimeAxisLabel));
yAxis.getTitle().setText(nullToEmptyString(Messages.AbstractSegmentStoreDensityViewer_CountAxisLabel));
xAxis.getTitle().setForeground(foregroundColor);
yAxis.getTitle().setForeground(foregroundColor);
xAxis.getTick().setForeground(foregroundColor);
yAxis.getTick().setForeground(foregroundColor);
xAxis.getGrid().setStyle(LineStyle.DOT);
yAxis.getGrid().setStyle(LineStyle.DOT);
fSeriesType = IYAppearance.Type.BAR;
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 abstract @Nullable ISegmentStoreProvider getSegmentStoreProvider(ITmfTrace trace);
@Nullable
private static ITmfTrace getTrace() {
return TmfTraceManager.getInstance().getActiveTrace();
}
/**
* Set the type of series you want
* @param type the type, bar or area
* @since 4.1
*/
protected void setType(String type) {
switch (type) {
case Type.BAR:
fSeriesType = Type.BAR;
break;
case Type.AREA:
fSeriesType = Type.AREA;
break;
default:
break;
}
}
private synchronized void updateDisplay(String name, SegmentStoreWithRange<ISegment> data) {
ISeries series = fSeriesType.equals(Type.BAR) ? createSeries() : createAreaSeries(name);
int barWidth = 4;
int preWidth = fOverrideNbPoints == 0 ? fChart.getPlotArea().getBounds().width / barWidth : fOverrideNbPoints;
if (!fSeriesType.equals(Type.BAR)) {
preWidth += 2;
}
final int width = preWidth;
double[] xOrigSeries = new double[width];
double[] yOrigSeries = new double[width];
// Set a positive value that is greater than 0 and less than 1.0
Arrays.fill(yOrigSeries, Double.MIN_VALUE);
data.setComparator(SegmentComparators.INTERVAL_LENGTH_COMPARATOR);
ISegment maxSegment = data.getElement(SegmentStoreWithRange.LAST);
long maxLength = Long.MIN_VALUE;
if (maxSegment != null) {
maxLength = maxSegment.getLength();
} else {
for (ISegment segment : data) {
maxLength = Math.max(maxLength, segment.getLength());
}
if (maxLength == Long.MIN_VALUE) {
maxLength = 1;
}
}
double maxFactor = 1.0 / (maxLength + 1.0);
long minX = Long.MAX_VALUE;
for (ISegment segment : data) {
double xBox = segment.getLength() * maxFactor * width;
if (yOrigSeries[(int) xBox] < 1) {
yOrigSeries[(int) xBox] = 1;
} else {
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;
if (!fSeriesType.equals(Type.BAR)) {
xOrigSeries[i] += timeWidth / 2;
}
}
double maxY = Double.NEGATIVE_INFINITY;
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.
*/
Range currentDurationRange = fCurrentDurationRange;
if (Double.isFinite(currentDurationRange.lower) && Double.isFinite(currentDurationRange.upper)) {
xAxis.setRange(currentDurationRange);
} else {
xAxis.adjustRange();
}
xAxis.getTick().setFormat(DENSITY_TIME_FORMATTER);
ILegend legend = fChart.getLegend();
legend.setVisible(fSegmentStoreProviders.size() > 1);
legend.setPosition(SWT.BOTTOM);
/*
* Clamp range lower to 0.9 to make it log, 0.1 would be scientifically
* accurate, but we cannot have partial counts.
*/
for (ISeries internalSeries : fChart.getSeriesSet().getSeries()) {
double[] ySeries = internalSeries.getYSeries();
for (int i = 0; i < ySeries.length; i++) {
maxY = Math.max(maxY, ySeries[i]);
}
}
fChart.getAxisSet().getYAxis(0).setRange(new Range(0.9, Math.max(1.0, maxY)));
fChart.getAxisSet().getYAxis(0).enableLogScale(true);
fChart.redraw();
new Thread(() -> {
for (ISegmentStoreDensityViewerDataListener l : fListeners) {
l.chartUpdated();
}
}).start();
}
private ISeries createSeries() {
IBarSeries series = (IBarSeries) fChart.getSeriesSet().createSeries(SeriesType.BAR, Messages.AbstractSegmentStoreDensityViewer_SeriesLabel);
series.setVisible(true);
series.setBarPadding(0);
series.setBarColor(getColorForRGB(BAR_COLOR));
return series;
}
private ISeries createAreaSeries(String name) {
ILineSeries series = (ILineSeries) fChart.getSeriesSet().createSeries(SeriesType.LINE, name);
series.setVisible(true);
series.enableStep(true);
series.enableArea(true);
series.setSymbolType(PlotSymbolType.NONE);
RGB rgb = getColorForItem(name);
Color color = getColorForRGB(rgb);
series.setLineColor(color);
return series;
}
private static Color getColorForRGB(RGB rgb) {
String rgbString = rgb.toString();
Color color = COLOR_REGISTRY.get(rgbString);
if (color == null) {
COLOR_REGISTRY.put(rgbString, rgb);
color = Objects.requireNonNull(COLOR_REGISTRY.get(rgbString));
}
return color;
}
/**
* Get the color for a series
*
* @param name
* the series name
* @return The color in RGB
* @since 4.1
*/
public RGB getColorForItem(String name) {
if (fSegmentStoreProviders.size() == 1) {
return BAR_COLOR;
}
Set<String> keys = fSegmentStoreProviders.keySet();
int i = 0;
for (String key : keys) {
if (key.equals(name)) {
break;
}
i++;
}
float pos = (float) i / keys.size();
int index = Math.max((int) (PALETTE.getNbColors() * pos), 0) % PALETTE.getNbColors();
return Objects.requireNonNull(RGBAUtil.fromRGBAColor(PALETTE.get().get(index)).rgb);
}
@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(final Range durationRange) {
fCurrentDurationRange = durationRange;
final TmfTimeRange timeRange = fCurrentTimeRange;
computeDataAsync(timeRange, durationRange).thenAccept(data -> {
synchronized (fListeners) {
if (fCurrentTimeRange.equals(timeRange) && fCurrentDurationRange.equals(durationRange)) {
for (ISegmentStoreDensityViewerDataListener listener : fListeners) {
for (SegmentStoreWithRange<ISegment> value : data.values()) {
listener.selectedDataChanged(value);
}
}
}
}
});
}
/**
* Zoom to a range of latency durations in the viewer.
*
* @param durationRange
* a range of latency durations
*/
public void zoom(final Range durationRange) {
fCurrentDurationRange = durationRange;
final TmfTimeRange timeRange = fCurrentTimeRange;
computeDataAsync(timeRange, durationRange).thenAccept(data -> {
synchronized (fListeners) {
if (fCurrentTimeRange.equals(timeRange) && fCurrentDurationRange.equals(durationRange)) {
applyData(data);
}
}
});
}
private CompletableFuture<Map<String, SegmentStoreWithRange<ISegment>>> computeDataAsync(final TmfTimeRange timeRange, final Range durationRange) {
return CompletableFuture.supplyAsync(() -> computeData(timeRange, durationRange));
}
private @Nullable Map<String, SegmentStoreWithRange<ISegment>> computeData(final TmfTimeRange timeRange, final Range durationRange) {
Map<String, SegmentStoreWithRange<ISegment>> retVal = new HashMap<>();
for (Entry<String, ISegmentStoreProvider> entry : fSegmentStoreProviders.entrySet()) {
final ISegmentStoreProvider segmentProvider = Objects.requireNonNull(entry.getValue());
final ISegmentStore<ISegment> segStore = segmentProvider.getSegmentStore();
if (segStore == null) {
continue;
}
// Filter on the segment duration if necessary
if (durationRange.lower > Double.MIN_VALUE || durationRange.upper < Double.MAX_VALUE) {
Predicate<ISegment> predicate = segment -> segment.getLength() >= durationRange.lower && segment.getLength() <= durationRange.upper;
retVal.put(entry.getKey(), new SegmentStoreWithRange<>(segStore, timeRange, predicate));
} else {
retVal.put(entry.getKey(), new SegmentStoreWithRange<>(segStore, timeRange));
}
}
return retVal;
}
private void applyData(final Map<String, SegmentStoreWithRange<ISegment>> map) {
Set<Entry<String, SegmentStoreWithRange<ISegment>>> entrySet = map.entrySet();
entrySet.parallelStream().forEach(entry -> {
SegmentStoreWithRange<ISegment> data = Objects.requireNonNull(entry.getValue());
data.setComparator(SegmentComparators.INTERVAL_LENGTH_COMPARATOR);
Display.getDefault().asyncExec(() -> updateDisplay(entry.getKey(), data));
if (fSegmentStoreProviders.size() > 1) {
setType(Type.AREA);
} else {
setType(Type.BAR);
}
for (ISegmentStoreDensityViewerDataListener l : fListeners) {
l.viewDataChanged(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) {
fSegmentStoreProviders.clear();
if (ssp != null) {
fSegmentStoreProviders.put("", ssp); //$NON-NLS-1$
}
}
/**
* 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 parent = getTrace();
if (parent == null) {
return;
}
fCurrentTimeRange = NonNullUtils.checkNotNull(signal.getCurrentRange());
updateWindowRange(fCurrentTimeRange, false);
updateWithRange(fCurrentTimeRange);
}
/**
* Update the display range
*
* @param timeRange
* the range
* @since 1.2
*/
@VisibleForTesting
public void updateWithRange(final TmfTimeRange timeRange) {
fCurrentTimeRange = timeRange;
final Range durationRange = getDefaultRange();
fCurrentDurationRange = durationRange;
computeDataAsync(timeRange, durationRange).thenAccept(data -> {
synchronized (fListeners) {
if (fCurrentTimeRange.equals(timeRange) && fCurrentDurationRange.equals(durationRange)) {
applyData(data);
}
}
});
}
@Override
public void refresh() {
fChart.redraw();
}
@Override
public void dispose() {
if (!fChart.isDisposed()) {
fChart.dispose();
}
}
private void internalDispose() {
fSegmentStoreProviders.entrySet().forEach(entry -> {
IAnalysisProgressListener listener = fProgressListeners.get(entry.getKey());
if (listener != null) {
Objects.requireNonNull(entry.getValue()).removeListener(listener);
}
});
fProgressListeners.clear();
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) {
if (fTrace != signal.getTrace()) {
loadTrace(getTrace());
fTrace = signal.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()) {
loadTrace(getTrace());
fTrace = signal.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();
TmfTraceContext ctx = TmfTraceManager.getInstance().getCurrentTraceContext();
TmfTimeRange windowRange = ctx.getWindowRange();
innerLoadTrace(trace);
updateWindowRange(windowRange, true);
fTrace = trace;
fCurrentTimeRange = windowRange;
zoom(getDefaultRange());
}
private void innerLoadTrace(@Nullable ITmfTrace trace) {
Set<ISegmentStoreProvider> sps = new HashSet<>();
if (trace != null) {
boolean newTrace = !Objects.equals(trace, fTrace) || fSegmentStoreProviders.isEmpty();
if (newTrace) {
fTraces.clear();
fSegmentStoreProviders.clear();
for (ITmfTrace child : TmfTraceManager.getTraceSet(trace)) {
fTraces.add(child);
ISegmentStoreProvider segmentStoreProvider = getSegmentStoreProvider(child);
String name = child.getName();
if (segmentStoreProvider != null && name != null) {
fSegmentStoreProviders.put(name, segmentStoreProvider);
sps.add(segmentStoreProvider);
}
}
ISegmentStoreProvider segmentStoreProvider = getSegmentStoreProvider(trace);
String name = trace.getName();
if (segmentStoreProvider != null && name != null && !sps.contains(segmentStoreProvider)) {
fSegmentStoreProviders.put(name, segmentStoreProvider);
}
}
}
}
private void updateWindowRange(TmfTimeRange windowRange, boolean updateListeners) {
for (Entry<@NonNull String, @NonNull ISegmentStoreProvider> entry : fSegmentStoreProviders.entrySet()) {
ISegmentStoreProvider provider = Objects.requireNonNull(entry.getValue());
if (updateListeners) {
IAnalysisProgressListener listener = (segmentProvider, data) -> updateWithRange(windowRange);
provider.addListener(listener);
fProgressListeners.put(entry.getKey(), listener);
}
if (provider instanceof IAnalysisModule) {
((IAnalysisModule) provider).schedule();
}
}
}
private static Range getDefaultRange() {
return new Range(AbstractSegmentStoreDensityView.DEFAULT_RANGE.getFirst(), AbstractSegmentStoreDensityView.DEFAULT_RANGE.getSecond());
}
/**
* 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();
}
}
/**
* Force the number of points to a fixed value
*
* @param nbPoints
* The number of points to display, cannot be negative. 0 means use
* native resolution. any positive integer means that number of
* points
* @since 2.2
*/
public synchronized void setNbPoints(int nbPoints) {
if (nbPoints < 0) {
throw new IllegalArgumentException("Number of points cannot be negative"); //$NON-NLS-1$
}
fOverrideNbPoints = nbPoints;
updateWithRange(fCurrentTimeRange);
}
/**
* 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);
}
}