Commit | Line | Data |
---|---|---|
b23cbbfc MAL |
1 | /****************************************************************************** |
2 | * Copyright (c) 2015 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 | ||
10 | package org.eclipse.tracecompass.analysis.timing.ui.views.segmentstore.density; | |
11 | ||
12 | import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString; | |
13 | ||
14 | import java.util.ArrayList; | |
15 | import java.util.Arrays; | |
16 | import java.util.Collections; | |
17 | import java.util.Iterator; | |
18 | import java.util.List; | |
19 | import java.util.concurrent.CompletableFuture; | |
20 | ||
21 | import org.eclipse.jdt.annotation.Nullable; | |
22 | import org.eclipse.swt.SWT; | |
23 | import org.eclipse.swt.graphics.Color; | |
24 | import org.eclipse.swt.graphics.RGB; | |
25 | import org.eclipse.swt.widgets.Composite; | |
26 | import org.eclipse.swt.widgets.Display; | |
27 | import org.eclipse.tracecompass.analysis.timing.core.segmentstore.AbstractSegmentStoreAnalysisModule; | |
28 | import org.eclipse.tracecompass.analysis.timing.core.segmentstore.IAnalysisProgressListener; | |
29 | import org.eclipse.tracecompass.common.core.NonNullUtils; | |
30 | import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.density.DensityTimeFormat; | |
31 | import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.density.MouseDragZoomProvider; | |
32 | import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.density.MouseSelectionProvider; | |
33 | import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.density.SimpleTooltipProvider; | |
34 | import org.eclipse.tracecompass.segmentstore.core.ISegment; | |
35 | import org.eclipse.tracecompass.segmentstore.core.ISegmentStore; | |
36 | import org.eclipse.tracecompass.segmentstore.core.SegmentComparators; | |
37 | import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler; | |
38 | import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal; | |
39 | import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal; | |
40 | import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal; | |
41 | import org.eclipse.tracecompass.tmf.core.signal.TmfWindowRangeUpdatedSignal; | |
42 | import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; | |
43 | import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; | |
44 | import org.eclipse.tracecompass.tmf.core.trace.TmfTraceContext; | |
45 | import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; | |
46 | import org.eclipse.tracecompass.tmf.ui.viewers.TmfViewer; | |
47 | import org.swtchart.Chart; | |
48 | import org.swtchart.IAxis; | |
49 | import org.swtchart.IBarSeries; | |
50 | import org.swtchart.ISeries; | |
51 | import org.swtchart.ISeries.SeriesType; | |
52 | import org.swtchart.ISeriesSet; | |
53 | import org.swtchart.LineStyle; | |
54 | import org.swtchart.Range; | |
55 | ||
56 | import com.google.common.base.Predicate; | |
57 | import com.google.common.collect.Iterators; | |
58 | import com.google.common.collect.Lists; | |
59 | ||
60 | /** | |
61 | * Displays the segment store analysis data in a density chart. | |
62 | * | |
63 | * @author Matthew Khouzam | |
64 | * @author Marc-Andre Laperle | |
65 | * | |
66 | * @since 2.0 | |
67 | */ | |
68 | public abstract class AbstractSegmentStoreDensityViewer extends TmfViewer { | |
69 | ||
70 | private static final DensityTimeFormat DENSITY_TIME_FORMATTER = new DensityTimeFormat(); | |
71 | private static final RGB BAR_COLOR = new RGB(0x42, 0x85, 0xf4); | |
72 | private final Chart fChart; | |
73 | private final MouseDragZoomProvider fDragZoomProvider; | |
74 | private final MouseSelectionProvider fDragProvider; | |
75 | private final SimpleTooltipProvider fTooltipProvider; | |
76 | ||
77 | private @Nullable ITmfTrace fTrace; | |
78 | private @Nullable IAnalysisProgressListener fListener; | |
79 | private @Nullable AbstractSegmentStoreAnalysisModule fAnalysisModule; | |
80 | private TmfTimeRange fCurrentTimeRange = TmfTimeRange.NULL_RANGE; | |
81 | private List<ISegmentStoreDensityViewerDataListener> fListeners; | |
82 | ||
83 | /** | |
84 | * Constructs a new density viewer. | |
85 | * | |
86 | * @param parent | |
87 | * the parent of the viewer | |
88 | */ | |
89 | public AbstractSegmentStoreDensityViewer(Composite parent) { | |
90 | super(parent); | |
91 | fListeners = new ArrayList<>(); | |
92 | fChart = new Chart(parent, SWT.NONE); | |
93 | fChart.getLegend().setVisible(false); | |
94 | fChart.getTitle().setVisible(false); | |
95 | fChart.getAxisSet().getXAxis(0).getTitle().setText(nullToEmptyString(Messages.AbstractSegmentStoreDensityViewer_TimeAxisLabel)); | |
96 | fChart.getAxisSet().getYAxis(0).getTitle().setText(nullToEmptyString(Messages.AbstractSegmentStoreDensityViewer_CountAxisLabel)); | |
97 | fChart.getAxisSet().getXAxis(0).getGrid().setStyle(LineStyle.NONE); | |
98 | fChart.getAxisSet().getYAxis(0).getGrid().setStyle(LineStyle.NONE); | |
99 | ||
100 | fDragZoomProvider = new MouseDragZoomProvider(this); | |
101 | fDragZoomProvider.register(); | |
102 | fDragProvider = new MouseSelectionProvider(this); | |
103 | fDragProvider.register(); | |
104 | fTooltipProvider = new SimpleTooltipProvider(this); | |
105 | fTooltipProvider.register(); | |
106 | } | |
107 | ||
108 | /** | |
109 | * Returns the segment store analysis module | |
110 | * | |
111 | * @param trace | |
112 | * The trace to consider | |
113 | * @return the analysis module | |
114 | */ | |
115 | protected @Nullable abstract AbstractSegmentStoreAnalysisModule getSegmentStoreAnalysisModule(ITmfTrace trace); | |
116 | ||
117 | @Nullable | |
118 | private static ITmfTrace getTrace() { | |
119 | return TmfTraceManager.getInstance().getActiveTrace(); | |
120 | } | |
121 | ||
122 | private void updateDisplay(List<ISegment> data) { | |
123 | if (data.isEmpty()) { | |
124 | return; | |
125 | } | |
126 | IBarSeries series = (IBarSeries) fChart.getSeriesSet().createSeries(SeriesType.BAR, Messages.AbstractSegmentStoreDensityViewer_SeriesLabel); | |
127 | series.setVisible(true); | |
128 | series.setBarPadding(0); | |
129 | ||
130 | series.setBarColor(new Color(Display.getDefault(), BAR_COLOR)); | |
131 | int barWidth = 4; | |
132 | final int width = fChart.getPlotArea().getBounds().width / barWidth; | |
133 | double[] xOrigSeries = new double[width]; | |
134 | double[] yOrigSeries = new double[width]; | |
135 | Arrays.fill(yOrigSeries, 1.0); | |
136 | long maxLength = data.get(data.size() - 1).getLength(); | |
137 | double maxFactor = 1.0 / (maxLength + 1.0); | |
138 | long minX = Long.MAX_VALUE; | |
139 | for (ISegment segment : data) { | |
140 | double xBox = segment.getLength() * maxFactor * width; | |
141 | yOrigSeries[(int) xBox]++; | |
142 | minX = Math.min(minX, segment.getLength()); | |
143 | } | |
144 | for (int i = 0; i < width; i++) { | |
145 | xOrigSeries[i] = i * maxLength / width; | |
146 | } | |
147 | double maxY = Double.MIN_VALUE; | |
148 | for (int i = 0; i < width; i++) { | |
149 | maxY = Math.max(maxY, yOrigSeries[i]); | |
150 | } | |
151 | if (minX == maxLength) { | |
152 | maxLength++; | |
153 | minX--; | |
154 | } | |
155 | series.setYSeries(yOrigSeries); | |
156 | series.setXSeries(xOrigSeries); | |
157 | final IAxis xAxis = fChart.getAxisSet().getXAxis(0); | |
158 | /* | |
159 | * adjustrange appears to bring origin back since we pad the series with | |
160 | * 0s, not interesting. | |
161 | */ | |
162 | xAxis.adjustRange(); | |
163 | Range range = xAxis.getRange(); | |
164 | // fix for overly aggressive lower after an adjust range | |
165 | range.lower = minX - range.upper + maxLength; | |
166 | xAxis.setRange(range); | |
167 | xAxis.getTick().setFormat(DENSITY_TIME_FORMATTER); | |
168 | fChart.getAxisSet().getYAxis(0).setRange(new Range(1.0, maxY)); | |
169 | fChart.getAxisSet().getYAxis(0).enableLogScale(true); | |
170 | fChart.redraw(); | |
171 | } | |
172 | ||
173 | @Override | |
174 | public Chart getControl() { | |
175 | return fChart; | |
176 | } | |
177 | ||
178 | /** | |
179 | * Select a range of latency durations in the viewer. | |
180 | * | |
181 | * @param durationRange | |
182 | * a range of latency durations | |
183 | */ | |
184 | public void select(Range durationRange) { | |
185 | computeDataAsync(fCurrentTimeRange, durationRange).thenAccept((data) -> { | |
186 | for (ISegmentStoreDensityViewerDataListener listener : fListeners) { | |
187 | listener.dataSelectionChanged(data); | |
188 | } | |
189 | }); | |
190 | } | |
191 | ||
192 | /** | |
193 | * Zoom to a range of latency durations in the viewer. | |
194 | * | |
195 | * @param durationRange | |
196 | * a range of latency durations | |
197 | */ | |
198 | public void zoom(Range durationRange) { | |
199 | computeDataAsync(fCurrentTimeRange, durationRange).thenAccept((data) -> applyData(data)); | |
200 | } | |
201 | ||
202 | private CompletableFuture<List<ISegment>> computeDataAsync(final TmfTimeRange timeRange, final Range durationRange) { | |
203 | return CompletableFuture.supplyAsync(() -> computeData(timeRange, durationRange)); | |
204 | } | |
205 | ||
206 | private @Nullable ArrayList<ISegment> computeData(final TmfTimeRange timeRange, final Range durationRange) { | |
207 | final AbstractSegmentStoreAnalysisModule analysisModule = fAnalysisModule; | |
208 | if (analysisModule == null) { | |
209 | return null; | |
210 | } | |
211 | final ISegmentStore<ISegment> results = analysisModule.getResults(); | |
212 | if (results == null) { | |
213 | return null; | |
214 | } | |
215 | ||
216 | Iterator<ISegment> intersectingElements = results.getIntersectingElements(timeRange.getStartTime().getValue(), timeRange.getEndTime().getValue()).iterator(); | |
217 | ||
218 | if (durationRange.lower > Double.MIN_VALUE || durationRange.upper < Double.MAX_VALUE) { | |
219 | Predicate<? super ISegment> predicate = new Predicate<ISegment>() { | |
220 | @Override | |
221 | public boolean apply(@Nullable ISegment input) { | |
222 | return input != null && input.getLength() >= durationRange.lower && input.getLength() <= durationRange.upper; | |
223 | } | |
224 | }; | |
225 | intersectingElements = Iterators.<ISegment> filter(intersectingElements, predicate); | |
226 | } | |
227 | ||
228 | return Lists.newArrayList(intersectingElements); | |
229 | } | |
230 | ||
231 | private void applyData(final @Nullable List<ISegment> data) { | |
232 | if (data != null) { | |
233 | Collections.sort(data, SegmentComparators.INTERVAL_LENGTH_COMPARATOR); | |
234 | Display.getDefault().asyncExec(() -> updateDisplay(data)); | |
235 | for (ISegmentStoreDensityViewerDataListener l : fListeners) { | |
236 | l.dataChanged(data); | |
237 | } | |
238 | } | |
239 | } | |
240 | ||
241 | /** | |
242 | * Signal handler for handling of the window range signal. | |
243 | * | |
244 | * @param signal | |
245 | * The {@link TmfWindowRangeUpdatedSignal} | |
246 | */ | |
247 | @TmfSignalHandler | |
248 | public void windowRangeUpdated(@Nullable TmfWindowRangeUpdatedSignal signal) { | |
249 | if (signal == null) { | |
250 | return; | |
251 | } | |
252 | ITmfTrace trace = getTrace(); | |
253 | if (trace == null) { | |
254 | return; | |
255 | } | |
256 | fAnalysisModule = getSegmentStoreAnalysisModule(trace); | |
257 | fCurrentTimeRange = NonNullUtils.checkNotNull(signal.getCurrentRange()); | |
258 | updateWithRange(fCurrentTimeRange); | |
259 | } | |
260 | ||
261 | private void updateWithRange(final TmfTimeRange range) { | |
262 | computeDataAsync(range, new Range(Double.MIN_VALUE, Double.MAX_VALUE)).thenAccept((data) -> applyData(data)); | |
263 | } | |
264 | ||
265 | @Override | |
266 | public void refresh() { | |
267 | fChart.redraw(); | |
268 | } | |
269 | ||
270 | @Override | |
271 | public void dispose() { | |
272 | if (fAnalysisModule != null && fListener != null) { | |
273 | fAnalysisModule.removeListener(fListener); | |
274 | } | |
275 | fDragZoomProvider.deregister(); | |
276 | fTooltipProvider.deregister(); | |
277 | fDragProvider.deregister(); | |
278 | super.dispose(); | |
279 | } | |
280 | ||
281 | /** | |
282 | * Signal handler for handling of the trace opened signal. | |
283 | * | |
284 | * @param signal | |
285 | * The trace opened signal {@link TmfTraceOpenedSignal} | |
286 | */ | |
287 | @TmfSignalHandler | |
288 | public void traceOpened(TmfTraceOpenedSignal signal) { | |
289 | fTrace = signal.getTrace(); | |
290 | loadTrace(getTrace()); | |
291 | } | |
292 | ||
293 | /** | |
294 | * Signal handler for handling of the trace selected signal. | |
295 | * | |
296 | * @param signal | |
297 | * The trace selected signal {@link TmfTraceSelectedSignal} | |
298 | */ | |
299 | @TmfSignalHandler | |
300 | public void traceSelected(TmfTraceSelectedSignal signal) { | |
301 | if (fTrace != signal.getTrace()) { | |
302 | fTrace = signal.getTrace(); | |
303 | loadTrace(getTrace()); | |
304 | } | |
305 | } | |
306 | ||
307 | /** | |
308 | * Signal handler for handling of the trace closed signal. | |
309 | * | |
310 | * @param signal | |
311 | * The trace closed signal {@link TmfTraceClosedSignal} | |
312 | */ | |
313 | @TmfSignalHandler | |
314 | public void traceClosed(TmfTraceClosedSignal signal) { | |
315 | ||
316 | if (signal.getTrace() != fTrace) { | |
317 | return; | |
318 | } | |
319 | ||
320 | fTrace = null; | |
321 | clearContent(); | |
322 | } | |
323 | ||
324 | /** | |
325 | * A Method to load a trace into the viewer. | |
326 | * | |
327 | * @param trace | |
328 | * A trace to apply in the viewer | |
329 | */ | |
330 | protected void loadTrace(@Nullable ITmfTrace trace) { | |
331 | clearContent(); | |
332 | ||
333 | fTrace = trace; | |
334 | TmfTraceContext ctx = TmfTraceManager.getInstance().getCurrentTraceContext(); | |
335 | TmfTimeRange windowRange = ctx.getWindowRange(); | |
336 | fCurrentTimeRange = windowRange; | |
337 | ||
338 | if (trace != null) { | |
339 | fAnalysisModule = getSegmentStoreAnalysisModule(trace); | |
340 | final AbstractSegmentStoreAnalysisModule module = fAnalysisModule; | |
341 | if (module != null) { | |
342 | fListener = (activeAnalysis, data) -> updateWithRange(windowRange); | |
343 | module.addListener(fListener); | |
344 | module.schedule(); | |
345 | } | |
346 | } | |
347 | zoom(new Range(0, Long.MAX_VALUE)); | |
348 | } | |
349 | ||
350 | /** | |
351 | * Clears the view content. | |
352 | */ | |
353 | private void clearContent() { | |
354 | final Chart chart = fChart; | |
355 | if (!chart.isDisposed()) { | |
356 | ISeriesSet set = chart.getSeriesSet(); | |
357 | ISeries[] series = set.getSeries(); | |
358 | for (int i = 0; i < series.length; i++) { | |
359 | set.deleteSeries(series[i].getId()); | |
360 | } | |
361 | for (IAxis axis : chart.getAxisSet().getAxes()) { | |
362 | axis.setRange(new Range(0, 1)); | |
363 | } | |
364 | chart.redraw(); | |
365 | } | |
366 | } | |
367 | ||
368 | /** | |
369 | * Add a data listener. | |
370 | * | |
371 | * @param dataListener | |
372 | * the data listener to add | |
373 | */ | |
374 | public void addDataListener(ISegmentStoreDensityViewerDataListener dataListener) { | |
375 | fListeners.add(dataListener); | |
376 | } | |
377 | ||
378 | /** | |
379 | * Remove a data listener. | |
380 | * | |
381 | * @param dataListener | |
382 | * the data listener to remove | |
383 | */ | |
384 | public void removeDataListener(ISegmentStoreDensityViewerDataListener dataListener) { | |
385 | fListeners.remove(dataListener); | |
386 | } | |
387 | } |