timing.ui: Have SegmentStoreScatterGraphViewer use iterables
[deliverable/tracecompass.git] / analysis / org.eclipse.tracecompass.analysis.timing.ui / src / org / eclipse / tracecompass / analysis / timing / ui / views / segmentstore / scatter / AbstractSegmentStoreScatterGraphViewer.java
1 /******************************************************************************
2 * Copyright (c) 2015, 2016 Ericsson
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * France Lapointe Nguyen - Initial API and implementation
11 * Bernd Hufmann - Extracted abstract class from LatencyScatterGraphViewer
12 *******************************************************************************/
13
14 package org.eclipse.tracecompass.analysis.timing.ui.views.segmentstore.scatter;
15
16 import java.text.Format;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.NoSuchElementException;
22 import java.util.Objects;
23 import java.util.concurrent.atomic.AtomicInteger;
24
25 import org.eclipse.core.runtime.IProgressMonitor;
26 import org.eclipse.core.runtime.IStatus;
27 import org.eclipse.core.runtime.Status;
28 import org.eclipse.core.runtime.jobs.Job;
29 import org.eclipse.jdt.annotation.NonNull;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.eclipse.swt.widgets.Composite;
32 import org.eclipse.swt.widgets.Display;
33 import org.eclipse.tracecompass.analysis.timing.core.segmentstore.IAnalysisProgressListener;
34 import org.eclipse.tracecompass.analysis.timing.core.segmentstore.ISegmentStoreProvider;
35 import org.eclipse.tracecompass.analysis.timing.ui.views.segmentstore.SubSecondTimeWithUnitFormat;
36 import org.eclipse.tracecompass.internal.analysis.timing.ui.Activator;
37 import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.scatter.Messages;
38 import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.scatter.SegmentStoreScatterGraphTooltipProvider;
39 import org.eclipse.tracecompass.segmentstore.core.ISegment;
40 import org.eclipse.tracecompass.segmentstore.core.ISegmentStore;
41 import org.eclipse.tracecompass.segmentstore.core.SegmentComparators;
42 import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
43 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
44 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
45 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
46 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal;
47 import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
48 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
49 import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
50 import org.eclipse.tracecompass.tmf.ui.viewers.xycharts.linecharts.TmfCommonXLineChartViewer;
51 import org.swtchart.ILineSeries;
52 import org.swtchart.ILineSeries.PlotSymbolType;
53 import org.swtchart.ISeries.SeriesType;
54 import org.swtchart.ISeriesSet;
55 import org.swtchart.LineStyle;
56
57 import com.google.common.primitives.Doubles;
58
59 /**
60 * Displays the segment store provider data in a scatter graph
61 *
62 * @author France Lapointe Nguyen
63 * @author Matthew Khouzam - reduced memory usage
64 * @since 2.0
65 */
66 public abstract class AbstractSegmentStoreScatterGraphViewer extends TmfCommonXLineChartViewer {
67
68 private static final Format FORMAT = new SubSecondTimeWithUnitFormat();
69
70 private final AtomicInteger fDirty = new AtomicInteger();
71
72 private static final int UNKNOWN_SIZE = -1;
73
74 private final class CompactingSegmentStoreQuery extends Job {
75 private static final long MAX_POINTS = 1000;
76 private final long fStart;
77 private final long fEnd;
78
79 private CompactingSegmentStoreQuery(long start, long end) {
80 super(Messages.SegmentStoreScatterGraphViewer_compactTitle);
81 fStart = start;
82 fEnd = end;
83 }
84
85 @Override
86 protected IStatus run(@Nullable IProgressMonitor monitor) {
87 final IProgressMonitor statusMonitor = monitor;
88 try {
89 if (statusMonitor == null) {
90 return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Monitor is null"); //$NON-NLS-1$
91 }
92
93 ISegmentStoreProvider segmentProvider = getSegmentProvider();
94 final long startTime = fStart;
95 final long endTime = fEnd;
96 if (segmentProvider == null) {
97 redraw(statusMonitor, startTime, startTime, Collections.emptyList());
98 return new Status(IStatus.WARNING, Activator.PLUGIN_ID, "segment provider not available"); //$NON-NLS-1$
99 }
100
101 final ISegmentStore<ISegment> segStore = segmentProvider.getSegmentStore();
102 if (segStore == null) {
103 redraw(statusMonitor, startTime, startTime, Collections.emptyList());
104 return new Status(IStatus.INFO, Activator.PLUGIN_ID, "Segment provider does not have segments"); //$NON-NLS-1$
105 }
106
107 fPixelStart = startTime;
108 fPixelSize = Math.max(1, (endTime - startTime) / MAX_POINTS);
109 final Iterable<ISegment> intersectingElements = segStore.getIntersectingElements(startTime, endTime, SegmentComparators.INTERVAL_START_COMPARATOR);
110 final Iterable<ISegment> displayData = compactList(startTime, intersectingElements);
111
112 redraw(statusMonitor, startTime, endTime, displayData);
113
114 if (statusMonitor.isCanceled()) {
115 return Status.CANCEL_STATUS;
116 }
117 return Status.OK_STATUS;
118 } finally {
119 /*
120 * fDirty should have been incremented before creating a job, so
121 * we decrement it once the job is done
122 */
123 fDirty.decrementAndGet();
124 }
125
126 }
127
128 private void redraw(final IProgressMonitor statusMonitor, final long startTime, final long endTime, final Iterable<@NonNull ISegment> displayData) {
129 fDisplayData = displayData;
130 /*
131 * Increment at every redraw, since the content of the view is not
132 * current
133 */
134 fDirty.incrementAndGet();
135 Display.getDefault().asyncExec(new Runnable() {
136
137 @Override
138 public void run() {
139 try {
140 updateData(startTime, endTime, UNKNOWN_SIZE, statusMonitor);
141 } finally {
142 /* Decrement once the redraw is done */
143 fDirty.decrementAndGet();
144 }
145 }
146 });
147 }
148
149 private Iterable<ISegment> compactList(final long startTime, final Iterable<@NonNull ISegment> iterableToCompact) {
150
151 return new Iterable<@NonNull ISegment>() {
152
153 @Override
154 public Iterator<ISegment> iterator() {
155
156 return new Iterator<@NonNull ISegment>() {
157
158 private @Nullable ISegment fLast = null;
159 private @Nullable ISegment fNext = null;
160 private Iterator<@NonNull ISegment> fIterator = iterableToCompact.iterator();
161
162 @Override
163 public @NonNull ISegment next() {
164 /* hasNext implies next != null */
165 if (hasNext()) {
166 fLast = fNext;
167 fNext = null;
168 return Objects.requireNonNull(fLast);
169 }
170 throw new NoSuchElementException();
171 }
172
173 @Override
174 public boolean hasNext() {
175 if (fLast == null) {
176 // iteration hasn't started yet.
177 if (fIterator.hasNext()) {
178 fLast = fIterator.next();
179 if (fLast.getStart() >= startTime) {
180 fNext = fLast;
181 }
182 } else {
183 return false;
184 }
185 }
186
187 // clear warning in calling overlaps below.
188 ISegment prev = fLast;
189 while (fNext == null && fIterator.hasNext()) {
190 ISegment tmp = fIterator.next();
191 if (tmp.getStart() >= startTime && !overlaps(prev, tmp)) {
192 fNext = tmp;
193 }
194 }
195 return fNext != null;
196 }
197 };
198 }
199 };
200 }
201
202 private boolean overlaps(ISegment last, ISegment next) {
203 long timePerPix = fPixelSize;
204 final long start = last.getStart();
205 final long pixelStart = fPixelStart;
206 final long pixelDuration = start - pixelStart;
207 long startPixBoundL = pixelDuration / timePerPix * timePerPix + pixelStart;
208 long startPixBoundR = startPixBoundL + timePerPix;
209 final long currentStart = next.getStart();
210 if (currentStart >= startPixBoundL && currentStart <= startPixBoundR) {
211 long length = last.getLength();
212 long lengthNext = next.getLength();
213 long lengthLow = length / timePerPix * timePerPix;
214 long lengthHigh = lengthLow + timePerPix;
215 return (lengthNext >= lengthLow && lengthNext <= lengthHigh);
216 }
217 return false;
218 }
219 }
220
221 // ------------------------------------------------------------------------
222 // Attributes
223 // ------------------------------------------------------------------------
224
225 /**
226 * Listener to update the model with the segment store provider results once
227 * its segment store is fully completed
228 */
229 private final class SegmentStoreProviderProgressListener implements IAnalysisProgressListener {
230
231 @Override
232 public void onComplete(ISegmentStoreProvider segmentProvider, ISegmentStore<ISegment> segmentStore) {
233 // Only update the model if trace that was analyzed is active trace
234 if (segmentProvider.equals(getSegmentProvider())) {
235 updateModel(segmentStore);
236 }
237 }
238 }
239
240 private long fPixelSize = -1;
241
242 private long fPixelStart = 0;
243 /**
244 * Data to display
245 */
246 private Iterable<@NonNull ISegment> fDisplayData = Collections.emptyList();
247
248 /**
249 * Provider completion listener
250 */
251 private SegmentStoreProviderProgressListener fListener;
252
253 /**
254 * Current segment provider
255 */
256 private @Nullable ISegmentStoreProvider fSegmentProvider;
257
258 private @Nullable Job fCompactingJob;
259
260 // ------------------------------------------------------------------------
261 // Constructor
262 // ------------------------------------------------------------------------
263
264 /**
265 * Constructor
266 *
267 * @param parent
268 * parent composite
269 * @param title
270 * name of the graph
271 * @param xLabel
272 * name of the x axis
273 * @param yLabel
274 * name of the y axis
275 */
276 public AbstractSegmentStoreScatterGraphViewer(Composite parent, String title, String xLabel, String yLabel) {
277 super(parent, title, xLabel, yLabel);
278 setTooltipProvider(new SegmentStoreScatterGraphTooltipProvider(this));
279 fListener = new SegmentStoreProviderProgressListener();
280 ITmfTrace trace = TmfTraceManager.getInstance().getActiveTrace();
281 initializeProvider(trace);
282 getSwtChart().getLegend().setVisible(false);
283 getSwtChart().getAxisSet().getYAxis(0).getTick().setFormat(FORMAT);
284 }
285
286 private final void initializeProvider(@Nullable ITmfTrace trace) {
287 if (trace != null) {
288 final ISegmentStoreProvider segmentStoreProvider = getSegmentStoreProvider(trace);
289 if (segmentStoreProvider != null) {
290 segmentStoreProvider.addListener(fListener);
291 setData(segmentStoreProvider);
292 }
293 }
294 }
295
296 // ------------------------------------------------------------------------
297 // Operations
298 // ------------------------------------------------------------------------
299
300 /**
301 * Update the data in the graph
302 *
303 * @param dataInput
304 * new model
305 */
306 public void updateModel(@Nullable ISegmentStore<ISegment> dataInput) {
307 // Update new window range
308 TmfTimeRange currentRange = TmfTraceManager.getInstance().getCurrentTraceContext().getWindowRange();
309 long currentStart = currentRange.getStartTime().toNanos();
310 long currentEnd = currentRange.getEndTime().toNanos();
311 if (dataInput == null) {
312 if (!getDisplay().isDisposed()) {
313 Display.getDefault().syncExec(new Runnable() {
314 @Override
315 public void run() {
316 clearContent();
317 }
318 });
319 }
320 fDisplayData = Collections.emptyList();
321 }
322 setWindowRange(currentStart, currentEnd);
323 updateContent();
324 }
325
326 @Override
327 protected void initializeDataSource() {
328 ITmfTrace trace = getTrace();
329 initializeProvider(trace);
330 if (trace != null) {
331 setData(getSegmentStoreProvider(trace));
332 }
333 }
334
335 @Override
336 protected void updateData(final long start, final long end, int nb, @Nullable IProgressMonitor monitor) {
337 if (end == start) {
338 return;
339 }
340 // Determine data that needs to be visible
341 List<Double> xSeries = nb != UNKNOWN_SIZE ? new ArrayList<>(nb) : new ArrayList<>();
342 List<Double> ySeries = nb != UNKNOWN_SIZE ? new ArrayList<>(nb) : new ArrayList<>();
343 // For each visible segments, add start time to x value and duration
344 // for y value
345 for (ISegment segment : fDisplayData) {
346 if (monitor != null && monitor.isCanceled()) {
347 return;
348 }
349 xSeries.add((double) (segment.getStart() - start));
350 ySeries.add((double) segment.getLength());
351 }
352 setXAxis(Doubles.toArray(xSeries));
353 setSeries(Messages.SegmentStoreScatterGraphViewer_legend, Doubles.toArray(ySeries));
354 updateDisplay();
355 }
356
357 @Override
358 protected void setWindowRange(final long windowStartTime, final long windowEndTime) {
359 super.setWindowRange(windowStartTime, windowEndTime);
360 }
361
362 @Override
363 protected ILineSeries addSeries(@Nullable String seriesName) {
364 ISeriesSet seriesSet = getSwtChart().getSeriesSet();
365 ILineSeries series = (ILineSeries) seriesSet.createSeries(SeriesType.LINE, seriesName);
366 series.setVisible(true);
367 series.enableArea(false);
368 series.setLineStyle(LineStyle.NONE);
369 series.setSymbolType(PlotSymbolType.DIAMOND);
370 return series;
371 }
372
373 /**
374 * Set the data into the viewer. If the provider is an analysis, it will
375 * update the model if the analysis is completed or run the analysis if not
376 * completed
377 *
378 * @param provider
379 * Segment store provider
380 */
381 public void setData(@Nullable ISegmentStoreProvider provider) {
382 if (provider == null) {
383 updateModel(null);
384 return;
385 }
386 ISegmentStore<ISegment> segStore = provider.getSegmentStore();
387 // If results are not null, then segment store is completed and model
388 // can be updated
389 if (segStore != null) {
390 updateModel(segStore);
391 setSegmentProvider(provider);
392 return;
393 }
394 updateModel(null);
395 provider.addListener(fListener);
396 if (provider instanceof IAnalysisModule) {
397 ((IAnalysisModule) provider).schedule();
398 }
399 setSegmentProvider(provider);
400 }
401
402 /**
403 * Returns the segment store provider
404 *
405 * @param trace
406 * The trace to consider
407 * @return the segment store provider
408 */
409 protected @Nullable abstract ISegmentStoreProvider getSegmentStoreProvider(ITmfTrace trace);
410
411 // ------------------------------------------------------------------------
412 // Signal handlers
413 // ------------------------------------------------------------------------
414
415 /**
416 * @param signal
417 * Signal received when a different trace is selected
418 */
419 @Override
420 @TmfSignalHandler
421 public void traceSelected(@Nullable TmfTraceSelectedSignal signal) {
422 super.traceSelected(signal);
423 if (signal == null) {
424 return;
425 }
426 ITmfTrace trace = signal.getTrace();
427 setTrace(trace);
428 if (trace != null) {
429 final TmfTimeRange timeRange = TmfTraceManager.getInstance().getCurrentTraceContext().getWindowRange();
430 setWindowRange(
431 timeRange.getStartTime().toNanos(),
432 timeRange.getEndTime().toNanos());
433 setData(getSegmentStoreProvider(trace));
434 updateContent();
435 }
436 }
437
438 /**
439 * @param signal
440 * Signal received when trace is opened
441 */
442 @Override
443 @TmfSignalHandler
444 public void traceOpened(@Nullable TmfTraceOpenedSignal signal) {
445 super.traceOpened(signal);
446 if (signal == null) {
447 return;
448 }
449 ITmfTrace trace = signal.getTrace();
450 setTrace(trace);
451 if (trace != null) {
452
453 final ISegmentStoreProvider segmentStoreProvider = getSegmentStoreProvider(trace);
454 final TmfTimeRange timeRange = TmfTraceManager.getInstance().getCurrentTraceContext().getWindowRange();
455 setWindowRange(
456 timeRange.getStartTime().toNanos(),
457 timeRange.getEndTime().toNanos());
458 setData(segmentStoreProvider);
459 }
460
461 }
462
463 @Override
464 protected void updateContent() {
465 /*
466 * Update is requested, content is not up to date, fDirty will be
467 * decremented in the compacting job
468 */
469 fDirty.incrementAndGet();
470 Job compactingJob = fCompactingJob;
471 if (compactingJob != null && compactingJob.getState() == Job.RUNNING) {
472 compactingJob.cancel();
473 }
474 compactingJob = new CompactingSegmentStoreQuery(getWindowStartTime(), getWindowEndTime());
475 fCompactingJob = compactingJob;
476 compactingJob.schedule();
477 }
478
479 /**
480 * @param signal
481 * Signal received when last opened trace is closed
482 */
483 @Override
484 @TmfSignalHandler
485 public void traceClosed(@Nullable TmfTraceClosedSignal signal) {
486 super.traceClosed(signal);
487 if (signal != null) {
488 // Check if there is no more opened trace
489 if (TmfTraceManager.getInstance().getActiveTrace() == null) {
490 ISegmentStoreProvider provider = getSegmentProvider();
491 if (provider != null) {
492 provider.removeListener(fListener);
493 }
494 clearContent();
495 }
496 }
497 refresh();
498 }
499
500 private @Nullable ISegmentStoreProvider getSegmentProvider() {
501 return fSegmentProvider;
502 }
503
504 private void setSegmentProvider(ISegmentStoreProvider provider) {
505 fSegmentProvider = provider;
506 }
507
508 @Override
509 public boolean isDirty() {
510 /* Check the parent's or this view's own dirtiness */
511 return super.isDirty() || (fDirty.get() != 0);
512 }
513 }
This page took 0.04355 seconds and 5 git commands to generate.