1 /*******************************************************************************
2 * Copyright (c) 2013, 2017 Ericsson and others
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
.io
.IOException
;
21 import java
.net
.URISyntaxException
;
22 import java
.util
.Collection
;
23 import java
.util
.Collections
;
24 import java
.util
.LinkedHashMap
;
25 import java
.util
.LinkedHashSet
;
26 import java
.util
.List
;
29 import java
.util
.function
.UnaryOperator
;
31 import org
.apache
.commons
.io
.FileUtils
;
32 import org
.eclipse
.core
.resources
.IFile
;
33 import org
.eclipse
.core
.resources
.IFolder
;
34 import org
.eclipse
.core
.resources
.IProject
;
35 import org
.eclipse
.core
.resources
.IResource
;
36 import org
.eclipse
.core
.runtime
.CoreException
;
37 import org
.eclipse
.core
.runtime
.URIUtil
;
38 import org
.eclipse
.jdt
.annotation
.NonNull
;
39 import org
.eclipse
.jdt
.annotation
.NonNullByDefault
;
40 import org
.eclipse
.jdt
.annotation
.Nullable
;
41 import org
.eclipse
.tracecompass
.internal
.tmf
.core
.Activator
;
42 import org
.eclipse
.tracecompass
.tmf
.core
.TmfCommonConstants
;
43 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfEventFilterAppliedSignal
;
44 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfSelectionRangeUpdatedSignal
;
45 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfSignalHandler
;
46 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfSignalManager
;
47 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfTraceClosedSignal
;
48 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfTraceModelSignal
;
49 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfTraceOpenedSignal
;
50 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfTraceSelectedSignal
;
51 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfWindowRangeUpdatedSignal
;
52 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.ITmfTimestamp
;
53 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.TmfTimeRange
;
54 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.TmfTimestamp
;
55 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.experiment
.TmfExperiment
;
57 import com
.google
.common
.collect
.ImmutableSet
;
60 * Central trace manager for TMF. It tracks the currently opened traces and
61 * experiment, as well as the currently-selected time or time range and the
62 * current window time range for each one of those. It also tracks filters
63 * applied for each trace.
65 * It's a singleton class, so only one instance should exist (available via
66 * {@link #getInstance()}).
68 * @author Alexandre Montplaisir
71 public final class TmfTraceManager
{
73 // ------------------------------------------------------------------------
75 // ------------------------------------------------------------------------
77 private final Map
<ITmfTrace
, TmfTraceContext
> fTraces
;
79 /** The currently-selected trace. Should always be part of the trace map */
80 private @Nullable ITmfTrace fCurrentTrace
= null;
82 private static final String TEMP_DIR_NAME
= ".temp"; //$NON-NLS-1$
84 // ------------------------------------------------------------------------
86 // ------------------------------------------------------------------------
88 private TmfTraceManager() {
89 fTraces
= new LinkedHashMap
<>();
90 TmfSignalManager
.registerVIP(this);
93 /** Singleton instance */
94 private static @Nullable TmfTraceManager tm
= null;
97 * Get an instance of the trace manager.
99 * @return The trace manager
101 public static synchronized TmfTraceManager
getInstance() {
102 TmfTraceManager mgr
= tm
;
104 mgr
= new TmfTraceManager();
111 * Disposes the trace manager
115 public synchronized void dispose() {
116 TmfSignalManager
.deregister(this);
118 fCurrentTrace
= null;
121 // ------------------------------------------------------------------------
123 // ------------------------------------------------------------------------
126 * Get the currently selected trace (normally, the focused editor).
128 * @return The active trace, or <code>null</code> if there is no active
131 public synchronized @Nullable ITmfTrace
getActiveTrace() {
132 return fCurrentTrace
;
136 * Get the trace set of the currently active trace.
138 * @return The active trace set. Empty (but non-null) if there is no
139 * currently active trace.
140 * @see #getTraceSet(ITmfTrace)
142 public synchronized Collection
<ITmfTrace
> getActiveTraceSet() {
143 final ITmfTrace trace
= fCurrentTrace
;
144 return getTraceSet(trace
);
148 * Get the currently-opened traces, as an unmodifiable set.
150 * @return A set containing the opened traces
152 public synchronized Set
<ITmfTrace
> getOpenedTraces() {
153 return Collections
.unmodifiableSet(fTraces
.keySet());
157 * Get the editor file for an opened trace.
161 * @return the editor file or null if the trace is not opened
163 public synchronized @Nullable IFile
getTraceEditorFile(ITmfTrace trace
) {
164 TmfTraceContext ctx
= fTraces
.get(trace
);
166 return ctx
.getEditorFile();
172 * Get the {@link TmfTraceContext} of the current active trace. This can be
173 * used to retrieve the current active/selected time ranges and such.
175 * @return The trace's context.
178 public synchronized TmfTraceContext
getCurrentTraceContext() {
179 TmfTraceContext curCtx
= fTraces
.get(fCurrentTrace
);
180 if (curCtx
== null) {
181 /* There are no traces opened at the moment. */
182 return TmfTraceContext
.NULL_CONTEXT
;
188 * Get the {@link TmfTraceContext} of the given trace.
191 * The trace or experiment.
192 * @return The trace's context.
195 public synchronized TmfTraceContext
getTraceContext(ITmfTrace trace
) {
196 TmfTraceContext curCtx
= fTraces
.get(trace
);
197 if (curCtx
== null) {
198 /* The trace is not opened. */
199 return TmfTraceContext
.NULL_CONTEXT
;
204 // ------------------------------------------------------------------------
205 // Public utility methods
206 // ------------------------------------------------------------------------
209 * Get the trace set of a given trace. For a standard trace, this is simply
210 * an array with only that trace in it. For experiments, this is an array of
211 * all the traces contained in this experiment.
214 * The trace or experiment. If it is null, an empty collection
216 * @return The corresponding trace set.
218 public static Collection
<ITmfTrace
> getTraceSet(@Nullable ITmfTrace trace
) {
220 return ImmutableSet
.of();
222 List
<@NonNull ITmfTrace
> traces
= trace
.getChildren(ITmfTrace
.class);
223 if (traces
.size() > 0) {
224 return ImmutableSet
.copyOf(traces
);
226 return ImmutableSet
.of(trace
);
230 * Get the trace set of a given trace or experiment, including the
231 * experiment. For a standard trace, this is simply a set containing only
232 * that trace. For experiments, it is the set of all the traces contained in
233 * this experiment, along with the experiment.
236 * The trace or experiment. If it is null, an empty collection
238 * @return The corresponding trace set, including the experiment.
240 public static Collection
<ITmfTrace
> getTraceSetWithExperiment(@Nullable ITmfTrace trace
) {
242 return ImmutableSet
.of();
244 if (trace
instanceof TmfExperiment
) {
245 TmfExperiment exp
= (TmfExperiment
) trace
;
246 List
<ITmfTrace
> traces
= exp
.getTraces();
247 Set
<ITmfTrace
> alltraces
= new LinkedHashSet
<>(traces
);
249 return ImmutableSet
.copyOf(alltraces
);
251 return Collections
.singleton(trace
);
255 * Return the path (as a string) to the directory for supplementary files to
256 * use with a given trace. If no supplementary file directory has been
257 * configured, a temporary directory based on the trace's name will be
262 * @return The path to the supplementary file directory (trailing slash is
265 public static String
getSupplementaryFileDir(ITmfTrace trace
) {
266 IResource resource
= trace
.getResource();
267 if (resource
== null) {
268 return getTemporaryDir(trace
);
271 String supplDir
= null;
273 supplDir
= resource
.getPersistentProperty(TmfCommonConstants
.TRACE_SUPPLEMENTARY_FOLDER
);
274 } catch (CoreException e
) {
275 return getTemporaryDir(trace
);
277 return supplDir
+ File
.separator
;
281 * Refresh the supplementary files resources for a trace, so it can pick up
282 * new files that got created.
285 * The trace for which to refresh the supplementary files
287 public static void refreshSupplementaryFiles(ITmfTrace trace
) {
288 IResource resource
= trace
.getResource();
289 if (resource
!= null && resource
.exists()) {
290 String supplFolderPath
= getSupplementaryFileDir(trace
);
291 IProject project
= resource
.getProject();
292 /* Remove the project's path from the supplementary path dir */
293 if (!supplFolderPath
.startsWith(project
.getLocation().toOSString())) {
294 Activator
.logWarning(String
.format("Supplementary files folder for trace %s is not within the project.", trace
.getName())); //$NON-NLS-1$
297 IFolder supplFolder
= project
.getFolder(supplFolderPath
.substring(project
.getLocationURI().getPath().length()));
298 if (supplFolder
.exists()) {
300 supplFolder
.refreshLocal(IResource
.DEPTH_INFINITE
, null);
301 } catch (CoreException e
) {
302 Activator
.logError("Error refreshing resources", e
); //$NON-NLS-1$
309 * Delete the supplementary files of a given trace.
312 * The trace for which the supplementary files are to be deleted
315 public static void deleteSupplementaryFiles(ITmfTrace trace
) {
317 FileUtils
.cleanDirectory(new File(TmfTraceManager
.getSupplementaryFileDir(trace
)));
318 } catch (IOException e
) {
319 Activator
.logError("Error deleting supplementary files for trace " + trace
.getName(), e
); //$NON-NLS-1$
321 refreshSupplementaryFiles(trace
);
325 * Update the trace context of a given trace.
330 * the function to apply to the trace context's builder
333 public synchronized void updateTraceContext(ITmfTrace trace
, UnaryOperator
<TmfTraceContext
.Builder
> updater
) {
334 TmfTraceContext ctx
= getTraceContext(trace
);
335 if (!ctx
.equals(TmfTraceContext
.NULL_CONTEXT
)) {
336 fTraces
.put(trace
, checkNotNull(updater
.apply(ctx
.builder())).build());
340 // ------------------------------------------------------------------------
342 // ------------------------------------------------------------------------
345 * Signal handler for the traceOpened signal.
348 * The incoming signal
351 public synchronized void traceOpened(final TmfTraceOpenedSignal signal
) {
352 final ITmfTrace trace
= signal
.getTrace();
353 final IFile editorFile
= signal
.getEditorFile();
354 final ITmfTimestamp startTs
= trace
.getStartTime();
356 long offset
= trace
.getInitialRangeOffset().toNanos();
357 long endTime
= startTs
.toNanos() + offset
;
358 final TmfTimeRange selectionRange
= new TmfTimeRange(startTs
, startTs
);
359 final TmfTimeRange windowRange
= new TmfTimeRange(startTs
, TmfTimestamp
.fromNanos(endTime
));
361 final TmfTraceContext startCtx
= trace
.createTraceContext(selectionRange
, windowRange
, editorFile
, null);
363 fTraces
.put(trace
, startCtx
);
365 /* We also want to set the newly-opened trace as the active trace */
366 fCurrentTrace
= trace
;
377 public synchronized void signalReceived(final TmfTraceModelSignal signal
) {
378 fTraces
.forEach((t
, u
) -> u
.receive(signal
));
383 * Handler for the TmfTraceSelectedSignal.
386 * The incoming signal
389 public synchronized void traceSelected(final TmfTraceSelectedSignal signal
) {
390 final ITmfTrace newTrace
= signal
.getTrace();
391 if (!fTraces
.containsKey(newTrace
)) {
392 throw new RuntimeException();
394 fCurrentTrace
= newTrace
;
398 * Signal handler for the filterApplied signal.
401 * The incoming signal
404 public synchronized void filterApplied(TmfEventFilterAppliedSignal signal
) {
405 ITmfTrace trace
= signal
.getTrace();
409 updateTraceContext(trace
, builder
->
410 builder
.setFilter(signal
.getEventFilter()));
414 * Signal handler for the traceClosed signal.
417 * The incoming signal
420 public synchronized void traceClosed(final TmfTraceClosedSignal signal
) {
421 fTraces
.remove(signal
.getTrace());
422 if (fTraces
.size() == 0) {
423 fCurrentTrace
= null;
425 * In other cases, we should receive a traceSelected signal that
426 * will indicate which trace is the new one.
432 * Signal handler for the selection range signal.
434 * The current time of *all* traces whose range contains the requested new
435 * selection time range will be updated.
438 * The incoming signal
442 public synchronized void selectionRangeUpdated(final TmfSelectionRangeUpdatedSignal signal
) {
443 final ITmfTimestamp beginTs
= signal
.getBeginTime();
444 final ITmfTimestamp endTs
= signal
.getEndTime();
446 for (ITmfTrace trace
: fTraces
.keySet()) {
447 if (beginTs
.intersects(getValidTimeRange(trace
)) || endTs
.intersects(getValidTimeRange(trace
))) {
448 updateTraceContext(trace
, builder
->
449 builder
.setSelection(new TmfTimeRange(beginTs
, endTs
)));
455 * Signal handler for the window range signal.
457 * The current window time range of *all* valid traces will be updated to
458 * the new requested times.
461 * The incoming signal
465 public synchronized void windowRangeUpdated(final TmfWindowRangeUpdatedSignal signal
) {
466 for (ITmfTrace trace
: fTraces
.keySet()) {
467 final TmfTimeRange validTr
= getValidTimeRange(trace
);
468 if (validTr
== null) {
472 /* Determine the new time range */
473 TmfTimeRange targetTr
= signal
.getCurrentRange().getIntersection(validTr
);
474 if (targetTr
!= null) {
475 updateTraceContext(trace
, builder
->
476 builder
.setWindowRange(targetTr
));
481 // ------------------------------------------------------------------------
482 // Private utility methods
483 // ------------------------------------------------------------------------
486 * Return the valid time range of a trace (not the current window time
487 * range, but the range of all possible valid timestamps).
489 * For a real trace this is the whole range of the trace. For an experiment,
490 * it goes from the start time of the earliest trace to the end time of the
494 * The trace to check for
495 * @return The valid time span, or 'null' if the trace is not valid
497 private @Nullable TmfTimeRange
getValidTimeRange(ITmfTrace trace
) {
498 if (!fTraces
.containsKey(trace
)) {
499 /* Trace is not part of the currently opened traces */
503 List
<ITmfTrace
> traces
= trace
.getChildren(ITmfTrace
.class);
505 if (traces
.isEmpty()) {
506 /* "trace" is a single trace, return its time range directly */
507 return trace
.getTimeRange();
510 if (traces
.size() == 1) {
511 /* Trace is an experiment with only 1 trace */
512 return traces
.get(0).getTimeRange();
516 * Trace is an trace set with 2+ traces, so get the earliest start and
519 ITmfTimestamp start
= traces
.get(0).getStartTime();
520 ITmfTimestamp end
= traces
.get(0).getEndTime();
522 for (int i
= 1; i
< traces
.size(); i
++) {
523 ITmfTrace curTrace
= traces
.get(i
);
524 if (curTrace
.getStartTime().compareTo(start
) < 0) {
525 start
= curTrace
.getStartTime();
527 if (curTrace
.getEndTime().compareTo(end
) > 0) {
528 end
= curTrace
.getEndTime();
531 return new TmfTimeRange(start
, end
);
535 * Get the temporary directory path. If there is an instance of Eclipse
536 * running, the temporary directory will reside under the workspace.
538 * @return the temporary directory path suitable to be passed to the
539 * java.io.File constructor without a trailing separator
541 public static String
getTemporaryDirPath() {
542 // Get the workspace path from the properties
543 String property
= System
.getProperty("osgi.instance.area"); //$NON-NLS-1$
544 if (property
!= null) {
546 File dir
= URIUtil
.toFile(URIUtil
.fromString(property
));
547 dir
= new File(dir
.getAbsolutePath() + File
.separator
+ TEMP_DIR_NAME
);
551 return dir
.getAbsolutePath();
552 } catch (URISyntaxException e
) {
553 Activator
.logError(e
.getLocalizedMessage(), e
);
556 return System
.getProperty("java.io.tmpdir"); //$NON-NLS-1$
560 * Get a temporary directory based on a trace's name. We will create the
561 * directory if it doesn't exist, so that it's ready to be used.
563 private static String
getTemporaryDir(ITmfTrace trace
) {
564 String pathName
= getTemporaryDirPath() +
568 File dir
= new File(pathName
);