1 /*******************************************************************************
2 * Copyright (c) 2013, 2015 Ericsson
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
10 * Alexandre Montplaisir - Initial API and implementation
11 * Patrick Tasse - Support selection range
12 * Xavier Raynaud - Support filters tracking
13 *******************************************************************************/
15 package org
.eclipse
.tracecompass
.tmf
.core
.trace
;
17 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.checkNotNull
;
20 import java
.net
.URISyntaxException
;
21 import java
.util
.Collection
;
22 import java
.util
.Collections
;
23 import java
.util
.LinkedHashMap
;
24 import java
.util
.LinkedHashSet
;
25 import java
.util
.List
;
29 import org
.eclipse
.core
.resources
.IFile
;
30 import org
.eclipse
.core
.resources
.IFolder
;
31 import org
.eclipse
.core
.resources
.IProject
;
32 import org
.eclipse
.core
.resources
.IResource
;
33 import org
.eclipse
.core
.runtime
.CoreException
;
34 import org
.eclipse
.core
.runtime
.URIUtil
;
35 import org
.eclipse
.jdt
.annotation
.NonNull
;
36 import org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
;
37 import org
.eclipse
.tracecompass
.internal
.tmf
.core
.Activator
;
38 import org
.eclipse
.tracecompass
.tmf
.core
.TmfCommonConstants
;
39 import org
.eclipse
.tracecompass
.tmf
.core
.filter
.ITmfFilter
;
40 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfEventFilterAppliedSignal
;
41 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfRangeSynchSignal
;
42 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfSignalHandler
;
43 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfSignalManager
;
44 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfTimeSynchSignal
;
45 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfTraceClosedSignal
;
46 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfTraceOpenedSignal
;
47 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfTraceSelectedSignal
;
48 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.ITmfTimestamp
;
49 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.TmfTimeRange
;
50 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.TmfTimestamp
;
51 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.experiment
.TmfExperiment
;
53 import com
.google
.common
.collect
.ImmutableSet
;
56 * Central trace manager for TMF. It tracks the currently opened traces and
57 * experiment, as well as the currently-selected time or time range and the
58 * current window time range for each one of those. It also tracks filters
59 * applied for each trace.
61 * It's a singleton class, so only one instance should exist (available via
62 * {@link #getInstance()}).
64 * @author Alexandre Montplaisir
66 public final class TmfTraceManager
{
68 // ------------------------------------------------------------------------
70 // ------------------------------------------------------------------------
72 private final Map
<ITmfTrace
, TmfTraceContext
> fTraces
;
74 /** The currently-selected trace. Should always be part of the trace map */
75 private ITmfTrace fCurrentTrace
= null;
77 private static final String TEMP_DIR_NAME
= ".temp"; //$NON-NLS-1$
79 // ------------------------------------------------------------------------
81 // ------------------------------------------------------------------------
83 private TmfTraceManager() {
84 fTraces
= new LinkedHashMap
<>();
85 TmfSignalManager
.registerVIP(this);
88 /** Singleton instance */
89 private static TmfTraceManager tm
= null;
92 * Get an instance of the trace manager.
94 * @return The trace manager
96 public static synchronized TmfTraceManager
getInstance() {
98 tm
= new TmfTraceManager();
103 // ------------------------------------------------------------------------
105 // ------------------------------------------------------------------------
108 * @return The begin timestamp of selection
110 public ITmfTimestamp
getSelectionBeginTime() {
111 return getCurrentTraceContext().getSelectionBegin();
115 * @return The end timestamp of selection
117 public ITmfTimestamp
getSelectionEndTime() {
118 return getCurrentTraceContext().getSelectionEnd();
122 * Return the current window time range.
124 * @return the current window time range
126 public synchronized TmfTimeRange
getCurrentRange() {
127 return getCurrentTraceContext().getWindowRange();
131 * Gets the filter applied to the current trace
133 * @return a filter, or <code>null</code>
135 public synchronized ITmfFilter
getCurrentFilter() {
136 return getCurrentTraceContext().getFilter();
140 * Get the currently selected trace (normally, the focused editor).
142 * @return The active trace
144 public synchronized ITmfTrace
getActiveTrace() {
145 return fCurrentTrace
;
149 * Get the trace set of the currently active trace.
151 * @return The active trace set
152 * @see #getTraceSet(ITmfTrace)
154 public synchronized @NonNull Collection
<ITmfTrace
> getActiveTraceSet() {
155 final ITmfTrace trace
= fCurrentTrace
;
156 return getTraceSet(trace
);
160 * Get the currently-opened traces, as an unmodifiable set.
162 * @return A set containing the opened traces
164 public synchronized Set
<ITmfTrace
> getOpenedTraces() {
165 return Collections
.unmodifiableSet(fTraces
.keySet());
169 * Get the editor file for an opened trace.
173 * @return the editor file or null if the trace is not opened
175 public synchronized IFile
getTraceEditorFile(ITmfTrace trace
) {
176 TmfTraceContext ctx
= fTraces
.get(trace
);
178 return ctx
.getEditorFile();
183 private TmfTraceContext
getCurrentTraceContext() {
184 TmfTraceContext curCtx
= fTraces
.get(fCurrentTrace
);
185 if (curCtx
== null) {
186 /* There are no traces opened at the moment. */
187 return TmfTraceContext
.NULL_CONTEXT
;
192 // ------------------------------------------------------------------------
193 // Public utility methods
194 // ------------------------------------------------------------------------
197 * Get the trace set of a given trace. For a standard trace, this is simply
198 * an array with only that trace in it. For experiments, this is an array of
199 * all the traces contained in this experiment.
202 * The trace or experiment
203 * @return The corresponding trace set.
205 public static @NonNull Collection
<ITmfTrace
> getTraceSet(ITmfTrace trace
) {
207 return NonNullUtils
.checkNotNull(ImmutableSet
.<ITmfTrace
> of());
209 List
<ITmfTrace
> traces
= trace
.getChildren(ITmfTrace
.class);
210 if (traces
.size() > 0) {
211 return NonNullUtils
.checkNotNull(ImmutableSet
.copyOf(traces
));
213 return NonNullUtils
.checkNotNull(ImmutableSet
.of(trace
));
217 * Get the trace set of a given trace or experiment, including the
218 * experiment. For a standard trace, this is simply a set containing only
219 * that trace. For experiments, it is the set of all the traces contained in
220 * this experiment, along with the experiment.
223 * The trace or experiment
224 * @return The corresponding trace set, including the experiment.
226 public static @NonNull Collection
<ITmfTrace
> getTraceSetWithExperiment(ITmfTrace trace
) {
228 return checkNotNull(ImmutableSet
.<ITmfTrace
> of());
230 if (trace
instanceof TmfExperiment
) {
231 TmfExperiment exp
= (TmfExperiment
) trace
;
232 List
<ITmfTrace
> traces
= exp
.getTraces();
233 Set
<ITmfTrace
> alltraces
= new LinkedHashSet
<>(traces
);
235 return NonNullUtils
.checkNotNull(ImmutableSet
.copyOf(alltraces
));
237 return checkNotNull(Collections
.singleton(trace
));
241 * Return the path (as a string) to the directory for supplementary files to
242 * use with a given trace. If no supplementary file directory has been
243 * configured, a temporary directory based on the trace's name will be
248 * @return The path to the supplementary file directory (trailing slash is
251 public static String
getSupplementaryFileDir(ITmfTrace trace
) {
252 IResource resource
= trace
.getResource();
253 if (resource
== null) {
254 return getTemporaryDir(trace
);
257 String supplDir
= null;
259 supplDir
= resource
.getPersistentProperty(TmfCommonConstants
.TRACE_SUPPLEMENTARY_FOLDER
);
260 } catch (CoreException e
) {
261 return getTemporaryDir(trace
);
263 return supplDir
+ File
.separator
;
267 * Refresh the supplementary files resources for a trace, so it can pick up
268 * new files that got created.
271 * The trace for which to refresh the supplementary files
273 public static void refreshSupplementaryFiles(ITmfTrace trace
) {
274 IResource resource
= trace
.getResource();
275 if (resource
!= null && resource
.exists()) {
276 String supplFolderPath
= getSupplementaryFileDir(trace
);
277 IProject project
= resource
.getProject();
278 /* Remove the project's path from the supplementary path dir */
279 if (!supplFolderPath
.startsWith(project
.getLocationURI().getPath())) {
280 Activator
.logWarning(String
.format("Supplementary files folder for trace %s is not within the project.", trace
.getName())); //$NON-NLS-1$
283 IFolder supplFolder
= project
.getFolder(supplFolderPath
.substring(project
.getLocationURI().getPath().length()));
284 if (supplFolder
.exists()) {
286 supplFolder
.refreshLocal(IResource
.DEPTH_INFINITE
, null);
287 } catch (CoreException e
) {
288 Activator
.logError("Error refreshing resources", e
); //$NON-NLS-1$
294 // ------------------------------------------------------------------------
296 // ------------------------------------------------------------------------
299 * Signal handler for the traceOpened signal.
302 * The incoming signal
305 public synchronized void traceOpened(final TmfTraceOpenedSignal signal
) {
306 final ITmfTrace trace
= signal
.getTrace();
307 final IFile editorFile
= signal
.getEditorFile();
308 final ITmfTimestamp startTs
= trace
.getStartTime();
310 /* Calculate the initial time range */
311 final int SCALE
= ITmfTimestamp
.NANOSECOND_SCALE
;
312 long offset
= trace
.getInitialRangeOffset().normalize(0, SCALE
).getValue();
313 long endTime
= startTs
.normalize(0, SCALE
).getValue() + offset
;
314 final TmfTimeRange startTr
= new TmfTimeRange(startTs
, new TmfTimestamp(endTime
, SCALE
));
316 final TmfTraceContext startCtx
= new TmfTraceContext(startTs
, startTs
, startTr
, editorFile
);
318 fTraces
.put(trace
, startCtx
);
320 /* We also want to set the newly-opened trace as the active trace */
321 fCurrentTrace
= trace
;
325 * Handler for the TmfTraceSelectedSignal.
328 * The incoming signal
331 public synchronized void traceSelected(final TmfTraceSelectedSignal signal
) {
332 final ITmfTrace newTrace
= signal
.getTrace();
333 if (!fTraces
.containsKey(newTrace
)) {
334 throw new RuntimeException();
336 fCurrentTrace
= newTrace
;
340 * Signal handler for the filterApplied signal.
343 * The incoming signal
346 public synchronized void filterApplied(TmfEventFilterAppliedSignal signal
) {
347 final ITmfTrace newTrace
= signal
.getTrace();
348 TmfTraceContext context
= fTraces
.get(newTrace
);
349 if (context
== null) {
350 throw new RuntimeException();
352 fTraces
.put(newTrace
, new TmfTraceContext(context
, signal
.getEventFilter()));
356 * Signal handler for the traceClosed signal.
359 * The incoming signal
362 public synchronized void traceClosed(final TmfTraceClosedSignal signal
) {
363 fTraces
.remove(signal
.getTrace());
364 if (fTraces
.size() == 0) {
365 fCurrentTrace
= null;
367 * In other cases, we should receive a traceSelected signal that
368 * will indicate which trace is the new one.
374 * Signal handler for the TmfTimeSynchSignal signal.
376 * The current time of *all* traces whose range contains the requested new
377 * selection time range will be updated.
380 * The incoming signal
383 public synchronized void timeUpdated(final TmfTimeSynchSignal signal
) {
384 final ITmfTimestamp beginTs
= signal
.getBeginTime();
385 final ITmfTimestamp endTs
= signal
.getEndTime();
387 for (Map
.Entry
<ITmfTrace
, TmfTraceContext
> entry
: fTraces
.entrySet()) {
388 final ITmfTrace trace
= entry
.getKey();
389 if (beginTs
.intersects(getValidTimeRange(trace
)) || endTs
.intersects(getValidTimeRange(trace
))) {
390 TmfTraceContext prevCtx
= entry
.getValue();
391 TmfTraceContext newCtx
= new TmfTraceContext(prevCtx
, beginTs
, endTs
);
392 entry
.setValue(newCtx
);
398 * Signal handler for the TmfRangeSynchSignal signal.
400 * The current window time range of *all* valid traces will be updated to
401 * the new requested times.
404 * The incoming signal
407 public synchronized void timeRangeUpdated(final TmfRangeSynchSignal signal
) {
408 for (Map
.Entry
<ITmfTrace
, TmfTraceContext
> entry
: fTraces
.entrySet()) {
409 final ITmfTrace trace
= entry
.getKey();
410 final TmfTraceContext curCtx
= entry
.getValue();
412 final TmfTimeRange validTr
= getValidTimeRange(trace
);
414 /* Determine the new time range */
415 TmfTimeRange targetTr
= signal
.getCurrentRange().getIntersection(validTr
);
416 TmfTimeRange newTr
= (targetTr
== null ? curCtx
.getWindowRange() : targetTr
);
418 /* Update the values */
419 TmfTraceContext newCtx
= new TmfTraceContext(curCtx
, newTr
);
420 entry
.setValue(newCtx
);
424 // ------------------------------------------------------------------------
425 // Private utility methods
426 // ------------------------------------------------------------------------
429 * Return the valid time range of a trace (not the current window time
430 * range, but the range of all possible valid timestamps).
432 * For a real trace this is the whole range of the trace. For an experiment,
433 * it goes from the start time of the earliest trace to the end time of the
437 * The trace to check for
438 * @return The valid time span, or 'null' if the trace is not valid
440 private TmfTimeRange
getValidTimeRange(ITmfTrace trace
) {
441 if (!fTraces
.containsKey(trace
)) {
442 /* Trace is not part of the currently opened traces */
446 List
<ITmfTrace
> traces
= trace
.getChildren(ITmfTrace
.class);
448 if (traces
.isEmpty()) {
449 /* "trace" is a single trace, return its time range directly */
450 return trace
.getTimeRange();
453 if (traces
.size() == 1) {
454 /* Trace is an experiment with only 1 trace */
455 return traces
.get(0).getTimeRange();
459 * Trace is an trace set with 2+ traces, so get the earliest start and
462 ITmfTimestamp start
= traces
.get(0).getStartTime();
463 ITmfTimestamp end
= traces
.get(0).getEndTime();
465 for (int i
= 1; i
< traces
.size(); i
++) {
466 ITmfTrace curTrace
= traces
.get(i
);
467 if (curTrace
.getStartTime().compareTo(start
) < 0) {
468 start
= curTrace
.getStartTime();
470 if (curTrace
.getEndTime().compareTo(end
) > 0) {
471 end
= curTrace
.getEndTime();
474 return new TmfTimeRange(start
, end
);
478 * Get the temporary directory path. If there is an instance of Eclipse
479 * running, the temporary directory will reside under the workspace.
481 * @return the temporary directory path suitable to be passed to the
482 * java.io.File constructor without a trailing separator
484 public static String
getTemporaryDirPath() {
485 // Get the workspace path from the properties
486 String property
= System
.getProperty("osgi.instance.area"); //$NON-NLS-1$
487 if (property
!= null) {
489 File dir
= URIUtil
.toFile(URIUtil
.fromString(property
));
490 dir
= new File(dir
.getAbsolutePath() + File
.separator
+ TEMP_DIR_NAME
);
494 return dir
.getAbsolutePath();
495 } catch (URISyntaxException e
) {
496 Activator
.logError(e
.getLocalizedMessage(), e
);
499 return System
.getProperty("java.io.tmpdir"); //$NON-NLS-1$
503 * Get a temporary directory based on a trace's name. We will create the
504 * directory if it doesn't exist, so that it's ready to be used.
506 private static String
getTemporaryDir(ITmfTrace trace
) {
507 String pathName
= getTemporaryDirPath() +
511 File dir
= new File(pathName
);