/****************************************************************************** * 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 * * Contributors: * France Lapointe Nguyen - Initial API and implementation * Bernd Hufmann - Extracted abstract class from LatencyScatterGraphViewer *******************************************************************************/ package org.eclipse.tracecompass.analysis.timing.ui.views.segmentstore.scatter; import java.text.Format; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.annotation.Nullable; 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.internal.analysis.timing.ui.Activator; import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.scatter.Messages; import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.scatter.SegmentStoreScatterGraphTooltipProvider; 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.timestamp.TmfTimeRange; import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; import org.eclipse.tracecompass.tmf.ui.viewers.xycharts.linecharts.TmfCommonXLineChartViewer; import org.swtchart.ILineSeries; import org.swtchart.ILineSeries.PlotSymbolType; import org.swtchart.ISeries.SeriesType; import org.swtchart.ISeriesSet; import org.swtchart.LineStyle; import com.google.common.primitives.Doubles; /** * Displays the segment store provider data in a scatter graph * * @author France Lapointe Nguyen * @author Matthew Khouzam - reduced memory usage * @since 2.0 */ public abstract class AbstractSegmentStoreScatterGraphViewer extends TmfCommonXLineChartViewer { private static final Format FORMAT = new SubSecondTimeWithUnitFormat(); private final AtomicInteger fDirty = new AtomicInteger(); private final class CompactingSegmentStoreQuery extends Job { private static final long MAX_POINTS = 1000; private final long fStart; private final long fEnd; private CompactingSegmentStoreQuery(long start, long end) { super(Messages.SegmentStoreScatterGraphViewer_compactTitle); fStart = start; fEnd = end; } @Override protected IStatus run(@Nullable IProgressMonitor monitor) { final IProgressMonitor statusMonitor = monitor; try { if (statusMonitor == null) { return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Monitor is null"); //$NON-NLS-1$ } ISegmentStoreProvider segmentProvider = getSegmentProvider(); final long startTime = fStart; final long endTime = fEnd; if (segmentProvider == null) { redraw(statusMonitor, startTime, startTime, Collections.EMPTY_LIST); return new Status(IStatus.WARNING, Activator.PLUGIN_ID, "segment provider not available"); //$NON-NLS-1$ } final ISegmentStore<ISegment> segStore = segmentProvider.getSegmentStore(); if (segStore == null) { redraw(statusMonitor, startTime, startTime, Collections.EMPTY_LIST); return new Status(IStatus.INFO, Activator.PLUGIN_ID, "Segment provider does not have segments"); //$NON-NLS-1$ } fPixelStart = startTime; fPixelSize = Math.max(1, (endTime - startTime) / MAX_POINTS); final Iterable<ISegment> intersectingElements = segStore.getIntersectingElements(startTime, endTime); final List<ISegment> list = convertIterableToList(intersectingElements, statusMonitor); final List<ISegment> displayData = (!list.isEmpty()) ? compactList(startTime, list, statusMonitor) : list; redraw(statusMonitor, startTime, endTime, displayData); if (statusMonitor.isCanceled()) { return Status.CANCEL_STATUS; } return Status.OK_STATUS; } finally { /* * fDirty should have been incremented before creating a job, so * we decrement it once the job is done */ fDirty.decrementAndGet(); } } private void redraw(final IProgressMonitor statusMonitor, final long startTime, final long endTime, final List<ISegment> displayData) { fDisplayData = displayData; /* * Increment at every redraw, since the content of the view is not * current */ fDirty.incrementAndGet(); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { try { updateData(startTime, endTime, displayData.size(), statusMonitor); } finally { /* Decrement once the redraw is done */ fDirty.decrementAndGet(); } } }); } private List<ISegment> compactList(final long startTime, final List<ISegment> listToCompact, final IProgressMonitor statusMonitor) { List<ISegment> displayData = new ArrayList<>(); ISegment last = listToCompact.get(0); if (last.getStart() >= startTime) { displayData.add(last); } for (ISegment next : listToCompact) { if (next.getStart() < startTime) { continue; } if (statusMonitor.isCanceled()) { return Collections.EMPTY_LIST; } if (!overlaps(last, next)) { displayData.add(next); last = next; } } return displayData; } private List<ISegment> convertIterableToList(final Iterable<ISegment> iterable, final IProgressMonitor statusMonitor) { final List<ISegment> list = new ArrayList<>(); for (ISegment seg : iterable) { if (statusMonitor.isCanceled()) { return Collections.EMPTY_LIST; } list.add(seg); } Collections.sort(list, SegmentComparators.INTERVAL_START_COMPARATOR); return list; } private boolean overlaps(ISegment last, ISegment next) { long timePerPix = fPixelSize; final long start = last.getStart(); final long pixelStart = fPixelStart; final long pixelDuration = start - pixelStart; long startPixBoundL = pixelDuration / timePerPix * timePerPix + pixelStart; long startPixBoundR = startPixBoundL + timePerPix; final long currentStart = next.getStart(); if (currentStart >= startPixBoundL && currentStart <= startPixBoundR) { long length = last.getLength(); long lengthNext = next.getLength(); long lengthLow = length / timePerPix * timePerPix; long lengthHigh = lengthLow + timePerPix; return (lengthNext >= lengthLow && lengthNext <= lengthHigh); } return false; } } // ------------------------------------------------------------------------ // Attributes // ------------------------------------------------------------------------ /** * Listener to update the model with the segment store provider results once * its segment store is fully completed */ private final class SegmentStoreProviderProgressListener implements IAnalysisProgressListener { @Override public void onComplete(ISegmentStoreProvider segmentProvider, ISegmentStore<ISegment> segmentStore) { // Only update the model if trace that was analyzed is active trace if (segmentProvider.equals(getSegmentProvider())) { updateModel(segmentStore); } } } private long fPixelSize = -1; private long fPixelStart = 0; /** * Data to display */ private Collection<ISegment> fDisplayData = Collections.EMPTY_LIST; /** * Provider completion listener */ private SegmentStoreProviderProgressListener fListener; /** * Current segment provider */ private @Nullable ISegmentStoreProvider fSegmentProvider; private @Nullable Job fCompactingJob; // ------------------------------------------------------------------------ // Constructor // ------------------------------------------------------------------------ /** * Constructor * * @param parent * parent composite * @param title * name of the graph * @param xLabel * name of the x axis * @param yLabel * name of the y axis */ public AbstractSegmentStoreScatterGraphViewer(Composite parent, String title, String xLabel, String yLabel) { super(parent, title, xLabel, yLabel); setTooltipProvider(new SegmentStoreScatterGraphTooltipProvider(this)); fListener = new SegmentStoreProviderProgressListener(); ITmfTrace trace = TmfTraceManager.getInstance().getActiveTrace(); initializeProvider(trace); getSwtChart().getLegend().setVisible(false); getSwtChart().getAxisSet().getYAxis(0).getTick().setFormat(FORMAT); } private final void initializeProvider(@Nullable ITmfTrace trace) { if (trace != null) { final ISegmentStoreProvider segmentStoreProvider = getSegmentStoreProvider(trace); if (segmentStoreProvider != null) { segmentStoreProvider.addListener(fListener); setData(segmentStoreProvider); } } } // ------------------------------------------------------------------------ // Operations // ------------------------------------------------------------------------ /** * Update the data in the graph * * @param dataInput * new model */ public void updateModel(@Nullable ISegmentStore<ISegment> dataInput) { // Update new window range TmfTimeRange currentRange = TmfTraceManager.getInstance().getCurrentTraceContext().getWindowRange(); long currentStart = currentRange.getStartTime().toNanos(); long currentEnd = currentRange.getEndTime().toNanos(); if (dataInput == null) { if (!getDisplay().isDisposed()) { Display.getDefault().syncExec(new Runnable() { @Override public void run() { clearContent(); } }); } fDisplayData = Collections.EMPTY_LIST; } else { Collection<ISegment> elements = (Collection<ISegment>) dataInput.getIntersectingElements(currentStart, currentEnd); // getIntersectingElements can return an unsorted iterable, make // sure our collection is sorted ArrayList<ISegment> list = new ArrayList<>(elements); Collections.sort(list, SegmentComparators.INTERVAL_START_COMPARATOR); fDisplayData = list; } setWindowRange(currentStart, currentEnd); updateContent(); } @Override protected void initializeDataSource() { ITmfTrace trace = getTrace(); initializeProvider(trace); if (trace != null) { setData(getSegmentStoreProvider(trace)); } } @Override protected void updateData(final long start, final long end, int nb, @Nullable IProgressMonitor monitor) { // Third parameter is not used by implementation // Determine data that needs to be visible Collection<ISegment> data = fDisplayData; final int dataSize = (nb == 0) ? data.size() : nb; if (end == start) { return; } List<Double> xSeries = new ArrayList<>(dataSize); List<Double> ySeries = new ArrayList<>(dataSize); // For each visible segments, add start time to x value and duration // for y value Iterator<ISegment> modelIter = data.iterator(); while (modelIter.hasNext()) { ISegment segment = modelIter.next(); xSeries.add((double) (segment.getStart() - start)); ySeries.add((double) segment.getLength()); } setXAxis(Doubles.toArray(xSeries)); setSeries(Messages.SegmentStoreScatterGraphViewer_legend, Doubles.toArray(ySeries)); updateDisplay(); } @Override protected void setWindowRange(final long windowStartTime, final long windowEndTime) { super.setWindowRange(windowStartTime, windowEndTime); } @Override protected ILineSeries addSeries(@Nullable String seriesName) { ISeriesSet seriesSet = getSwtChart().getSeriesSet(); ILineSeries series = (ILineSeries) seriesSet.createSeries(SeriesType.LINE, seriesName); series.setVisible(true); series.enableArea(false); series.setLineStyle(LineStyle.NONE); series.setSymbolType(PlotSymbolType.DIAMOND); return series; } /** * Set the data into the viewer. If the provider is an analysis, it will * update the model if the analysis is completed or run the analysis if not * completed * * @param provider * Segment store provider */ public void setData(@Nullable ISegmentStoreProvider provider) { if (provider == null) { updateModel(null); return; } ISegmentStore<ISegment> segStore = provider.getSegmentStore(); // If results are not null, then segment store is completed and model // can be updated if (segStore != null) { updateModel(segStore); setSegmentProvider(provider); return; } updateModel(null); provider.addListener(fListener); if (provider instanceof IAnalysisModule) { ((IAnalysisModule) provider).schedule(); } setSegmentProvider(provider); } /** * Returns the segment store provider * * @param trace * The trace to consider * @return the segment store provider */ protected @Nullable abstract ISegmentStoreProvider getSegmentStoreProvider(ITmfTrace trace); // ------------------------------------------------------------------------ // Signal handlers // ------------------------------------------------------------------------ /** * @param signal * Signal received when a different trace is selected */ @Override @TmfSignalHandler public void traceSelected(@Nullable TmfTraceSelectedSignal signal) { super.traceSelected(signal); if (signal == null) { return; } ITmfTrace trace = signal.getTrace(); setTrace(trace); if (trace != null) { final TmfTimeRange timeRange = TmfTraceManager.getInstance().getCurrentTraceContext().getWindowRange(); setWindowRange( timeRange.getStartTime().toNanos(), timeRange.getEndTime().toNanos()); setData(getSegmentStoreProvider(trace)); updateContent(); } } /** * @param signal * Signal received when trace is opened */ @Override @TmfSignalHandler public void traceOpened(@Nullable TmfTraceOpenedSignal signal) { super.traceOpened(signal); if (signal == null) { return; } ITmfTrace trace = signal.getTrace(); setTrace(trace); if (trace != null) { final ISegmentStoreProvider segmentStoreProvider = getSegmentStoreProvider(trace); final TmfTimeRange timeRange = TmfTraceManager.getInstance().getCurrentTraceContext().getWindowRange(); setWindowRange( timeRange.getStartTime().toNanos(), timeRange.getEndTime().toNanos()); setData(segmentStoreProvider); } } @Override protected void updateContent() { /* * Update is requested, content is not up to date, fDirty will be * decremented in the compacting job */ fDirty.incrementAndGet(); Job compactingJob = fCompactingJob; if (compactingJob != null && compactingJob.getState() == Job.RUNNING) { compactingJob.cancel(); } compactingJob = new CompactingSegmentStoreQuery(getWindowStartTime(), getWindowEndTime()); fCompactingJob = compactingJob; compactingJob.schedule(); } /** * @param signal * Signal received when last opened trace is closed */ @Override @TmfSignalHandler public void traceClosed(@Nullable TmfTraceClosedSignal signal) { super.traceClosed(signal); if (signal != null) { // Check if there is no more opened trace if (TmfTraceManager.getInstance().getActiveTrace() == null) { ISegmentStoreProvider provider = getSegmentProvider(); if (provider != null) { provider.removeListener(fListener); } clearContent(); } } refresh(); } private @Nullable ISegmentStoreProvider getSegmentProvider() { return fSegmentProvider; } private void setSegmentProvider(ISegmentStoreProvider provider) { fSegmentProvider = provider; } @Override public boolean isDirty() { /* Check the parent's or this view's own dirtiness */ return super.isDirty() || (fDirty.get() != 0); } }