tmf: Fix concurrent data access violations
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.core / src / org / eclipse / tracecompass / tmf / core / trace / TmfTraceManager.java
CommitLineData
fc526aef 1/*******************************************************************************
ed902a2b 2 * Copyright (c) 2013, 2015 Ericsson
fc526aef
AM
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 * Alexandre Montplaisir - Initial API and implementation
0fcf3b09 11 * Patrick Tasse - Support selection range
d3de0920 12 * Xavier Raynaud - Support filters tracking
fc526aef
AM
13 *******************************************************************************/
14
2bdf0193 15package org.eclipse.tracecompass.tmf.core.trace;
fc526aef 16
5db5a3a4
AM
17import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
18
e1385db9 19import java.io.File;
cad998a7 20import java.io.IOException;
3ec38c4c 21import java.net.URISyntaxException;
c14c0757 22import java.util.Collection;
fab18d20 23import java.util.Collections;
fc526aef 24import java.util.LinkedHashMap;
0f8687b4 25import java.util.LinkedHashSet;
e2239aea 26import java.util.List;
fc526aef 27import java.util.Map;
fab18d20 28import java.util.Set;
fc526aef 29
cad998a7 30import org.apache.commons.io.FileUtils;
deaae6e1 31import org.eclipse.core.resources.IFile;
b5e8ee95
GB
32import org.eclipse.core.resources.IFolder;
33import org.eclipse.core.resources.IProject;
e1385db9
AM
34import org.eclipse.core.resources.IResource;
35import org.eclipse.core.runtime.CoreException;
1ac53e54 36import org.eclipse.core.runtime.URIUtil;
0f8687b4 37import org.eclipse.jdt.annotation.NonNull;
bcd8d4b1 38import org.eclipse.jdt.annotation.NonNullByDefault;
21852dfa 39import org.eclipse.jdt.annotation.Nullable;
2bdf0193
AM
40import org.eclipse.tracecompass.internal.tmf.core.Activator;
41import org.eclipse.tracecompass.tmf.core.TmfCommonConstants;
2bdf0193 42import org.eclipse.tracecompass.tmf.core.signal.TmfEventFilterAppliedSignal;
16801c72 43import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal;
2bdf0193
AM
44import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
45import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
2bdf0193 46import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
178d3c0e 47import org.eclipse.tracecompass.tmf.core.signal.TmfTraceModelSignal;
2bdf0193
AM
48import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
49import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal;
16801c72 50import org.eclipse.tracecompass.tmf.core.signal.TmfWindowRangeUpdatedSignal;
2bdf0193
AM
51import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
52import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
53import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
5c5fa260 54import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment;
fc526aef 55
c14c0757
GB
56import com.google.common.collect.ImmutableSet;
57
fc526aef
AM
58/**
59 * Central trace manager for TMF. It tracks the currently opened traces and
0fcf3b09 60 * experiment, as well as the currently-selected time or time range and the
d3de0920
XR
61 * current window time range for each one of those. It also tracks filters
62 * applied for each trace.
fc526aef
AM
63 *
64 * It's a singleton class, so only one instance should exist (available via
65 * {@link #getInstance()}).
66 *
67 * @author Alexandre Montplaisir
fc526aef 68 */
bcd8d4b1 69@NonNullByDefault
fc526aef
AM
70public final class TmfTraceManager {
71
72 // ------------------------------------------------------------------------
73 // Attributes
74 // ------------------------------------------------------------------------
75
76 private final Map<ITmfTrace, TmfTraceContext> fTraces;
77
78 /** The currently-selected trace. Should always be part of the trace map */
bcd8d4b1 79 private @Nullable ITmfTrace fCurrentTrace = null;
fc526aef 80
3ec38c4c
MAL
81 private static final String TEMP_DIR_NAME = ".temp"; //$NON-NLS-1$
82
fc526aef
AM
83 // ------------------------------------------------------------------------
84 // Constructor
85 // ------------------------------------------------------------------------
86
87 private TmfTraceManager() {
a4524c1b 88 fTraces = new LinkedHashMap<>();
fc526aef
AM
89 TmfSignalManager.registerVIP(this);
90 }
91
92 /** Singleton instance */
bcd8d4b1 93 private static @Nullable TmfTraceManager tm = null;
fc526aef
AM
94
95 /**
96 * Get an instance of the trace manager.
97 *
98 * @return The trace manager
99 */
100 public static synchronized TmfTraceManager getInstance() {
bcd8d4b1
AM
101 TmfTraceManager mgr = tm;
102 if (mgr == null) {
103 mgr = new TmfTraceManager();
104 tm = mgr;
fc526aef 105 }
bcd8d4b1 106 return mgr;
fc526aef
AM
107 }
108
ded2b27f
PT
109 /**
110 * Disposes the trace manager
111 *
5e479c4f 112 * @since 2.3
ded2b27f 113 */
8580d34f 114 public synchronized void dispose() {
ded2b27f
PT
115 TmfSignalManager.deregister(this);
116 fTraces.clear();
117 fCurrentTrace = null;
118 }
119
fc526aef
AM
120 // ------------------------------------------------------------------------
121 // Accessors
122 // ------------------------------------------------------------------------
123
fc526aef
AM
124 /**
125 * Get the currently selected trace (normally, the focused editor).
126 *
bcd8d4b1
AM
127 * @return The active trace, or <code>null</code> if there is no active
128 * trace
fc526aef 129 */
bcd8d4b1 130 public synchronized @Nullable ITmfTrace getActiveTrace() {
fc526aef
AM
131 return fCurrentTrace;
132 }
133
134 /**
a6fc3a28 135 * Get the trace set of the currently active trace.
fc526aef 136 *
bcd8d4b1
AM
137 * @return The active trace set. Empty (but non-null) if there is no
138 * currently active trace.
a6fc3a28 139 * @see #getTraceSet(ITmfTrace)
fc526aef 140 */
bcd8d4b1 141 public synchronized Collection<ITmfTrace> getActiveTraceSet() {
fc526aef 142 final ITmfTrace trace = fCurrentTrace;
a6fc3a28 143 return getTraceSet(trace);
fc526aef
AM
144 }
145
fab18d20
AM
146 /**
147 * Get the currently-opened traces, as an unmodifiable set.
148 *
149 * @return A set containing the opened traces
150 */
151 public synchronized Set<ITmfTrace> getOpenedTraces() {
152 return Collections.unmodifiableSet(fTraces.keySet());
153 }
154
deaae6e1
PT
155 /**
156 * Get the editor file for an opened trace.
157 *
158 * @param trace
159 * the trace
160 * @return the editor file or null if the trace is not opened
deaae6e1 161 */
bcd8d4b1 162 public synchronized @Nullable IFile getTraceEditorFile(ITmfTrace trace) {
deaae6e1
PT
163 TmfTraceContext ctx = fTraces.get(trace);
164 if (ctx != null) {
165 return ctx.getEditorFile();
166 }
167 return null;
168 }
169
21852dfa
AM
170 /**
171 * Get the {@link TmfTraceContext} of the current active trace. This can be
172 * used to retrieve the current active/selected time ranges and such.
173 *
174 * @return The trace's context.
175 * @since 1.0
176 */
177 public synchronized TmfTraceContext getCurrentTraceContext() {
fc526aef
AM
178 TmfTraceContext curCtx = fTraces.get(fCurrentTrace);
179 if (curCtx == null) {
180 /* There are no traces opened at the moment. */
181 return TmfTraceContext.NULL_CONTEXT;
182 }
183 return curCtx;
184 }
185
e1385db9
AM
186 // ------------------------------------------------------------------------
187 // Public utility methods
188 // ------------------------------------------------------------------------
189
a6fc3a28
AM
190 /**
191 * Get the trace set of a given trace. For a standard trace, this is simply
192 * an array with only that trace in it. For experiments, this is an array of
193 * all the traces contained in this experiment.
194 *
195 * @param trace
bcd8d4b1
AM
196 * The trace or experiment. If it is null, an empty collection
197 * will be returned.
c14c0757 198 * @return The corresponding trace set.
a6fc3a28 199 */
bcd8d4b1 200 public static Collection<ITmfTrace> getTraceSet(@Nullable ITmfTrace trace) {
a6fc3a28 201 if (trace == null) {
0e4f957e 202 return ImmutableSet.of();
a6fc3a28 203 }
df2597e0 204 List<@NonNull ITmfTrace> traces = trace.getChildren(ITmfTrace.class);
e2239aea 205 if (traces.size() > 0) {
0e4f957e 206 return ImmutableSet.copyOf(traces);
a6fc3a28 207 }
0e4f957e 208 return ImmutableSet.of(trace);
a6fc3a28
AM
209 }
210
0f8687b4
GB
211 /**
212 * Get the trace set of a given trace or experiment, including the
213 * experiment. For a standard trace, this is simply a set containing only
214 * that trace. For experiments, it is the set of all the traces contained in
215 * this experiment, along with the experiment.
216 *
217 * @param trace
bcd8d4b1
AM
218 * The trace or experiment. If it is null, an empty collection
219 * will be returned.
c14c0757 220 * @return The corresponding trace set, including the experiment.
0f8687b4 221 */
bcd8d4b1 222 public static Collection<ITmfTrace> getTraceSetWithExperiment(@Nullable ITmfTrace trace) {
0f8687b4 223 if (trace == null) {
0e4f957e 224 return ImmutableSet.of();
0f8687b4
GB
225 }
226 if (trace instanceof TmfExperiment) {
227 TmfExperiment exp = (TmfExperiment) trace;
fa62dc1d
BH
228 List<ITmfTrace> traces = exp.getTraces();
229 Set<ITmfTrace> alltraces = new LinkedHashSet<>(traces);
0f8687b4 230 alltraces.add(exp);
0e4f957e 231 return ImmutableSet.copyOf(alltraces);
0f8687b4 232 }
0e4f957e 233 return Collections.singleton(trace);
0f8687b4
GB
234 }
235
e1385db9
AM
236 /**
237 * Return the path (as a string) to the directory for supplementary files to
238 * use with a given trace. If no supplementary file directory has been
239 * configured, a temporary directory based on the trace's name will be
240 * provided.
241 *
242 * @param trace
243 * The trace
244 * @return The path to the supplementary file directory (trailing slash is
245 * INCLUDED!)
246 */
247 public static String getSupplementaryFileDir(ITmfTrace trace) {
248 IResource resource = trace.getResource();
249 if (resource == null) {
250 return getTemporaryDir(trace);
251 }
252
253 String supplDir = null;
254 try {
255 supplDir = resource.getPersistentProperty(TmfCommonConstants.TRACE_SUPPLEMENTARY_FOLDER);
256 } catch (CoreException e) {
257 return getTemporaryDir(trace);
258 }
259 return supplDir + File.separator;
260 }
261
b5e8ee95
GB
262 /**
263 * Refresh the supplementary files resources for a trace, so it can pick up
264 * new files that got created.
265 *
266 * @param trace
267 * The trace for which to refresh the supplementary files
b5e8ee95
GB
268 */
269 public static void refreshSupplementaryFiles(ITmfTrace trace) {
270 IResource resource = trace.getResource();
88567ed2 271 if (resource != null && resource.exists()) {
b5e8ee95
GB
272 String supplFolderPath = getSupplementaryFileDir(trace);
273 IProject project = resource.getProject();
274 /* Remove the project's path from the supplementary path dir */
cdb43c16 275 if (!supplFolderPath.startsWith(project.getLocation().toOSString())) {
b5e8ee95
GB
276 Activator.logWarning(String.format("Supplementary files folder for trace %s is not within the project.", trace.getName())); //$NON-NLS-1$
277 return;
278 }
279 IFolder supplFolder = project.getFolder(supplFolderPath.substring(project.getLocationURI().getPath().length()));
280 if (supplFolder.exists()) {
281 try {
282 supplFolder.refreshLocal(IResource.DEPTH_INFINITE, null);
283 } catch (CoreException e) {
284 Activator.logError("Error refreshing resources", e); //$NON-NLS-1$
285 }
286 }
287 }
288 }
289
cad998a7
AM
290 /**
291 * Delete the supplementary files of a given trace.
292 *
293 * @param trace
294 * The trace for which the supplementary files are to be deleted
295 * @since 2.2
296 */
297 public static void deleteSupplementaryFiles(ITmfTrace trace) {
298 try {
299 FileUtils.cleanDirectory(new File(TmfTraceManager.getSupplementaryFileDir(trace)));
300 } catch (IOException e) {
301 Activator.logError("Error deleting supplementary files for trace " + trace.getName(), e); //$NON-NLS-1$
302 }
303 refreshSupplementaryFiles(trace);
304 }
305
fc526aef
AM
306 // ------------------------------------------------------------------------
307 // Signal handlers
308 // ------------------------------------------------------------------------
309
310 /**
311 * Signal handler for the traceOpened signal.
312 *
313 * @param signal
314 * The incoming signal
315 */
316 @TmfSignalHandler
317 public synchronized void traceOpened(final TmfTraceOpenedSignal signal) {
318 final ITmfTrace trace = signal.getTrace();
deaae6e1 319 final IFile editorFile = signal.getEditorFile();
fc526aef
AM
320 final ITmfTimestamp startTs = trace.getStartTime();
321
16801c72
MK
322 long offset = trace.getInitialRangeOffset().toNanos();
323 long endTime = startTs.toNanos() + offset;
21852dfa 324 final TmfTimeRange selectionRange = new TmfTimeRange(startTs, startTs);
b2c971ec 325 final TmfTimeRange windowRange = new TmfTimeRange(startTs, TmfTimestamp.fromNanos(endTime));
fc526aef 326
ccc49be1 327 final TmfTraceContext startCtx = trace.createTraceContext(selectionRange, windowRange, editorFile, null);
fc526aef
AM
328
329 fTraces.put(trace, startCtx);
330
331 /* We also want to set the newly-opened trace as the active trace */
332 fCurrentTrace = trace;
333 }
334
ccc49be1
MK
335 /**
336 * Signal propagator
bcd8d4b1
AM
337 *
338 * @param signal
339 * any signal
ccc49be1
MK
340 * @since 2.0
341 */
342 @TmfSignalHandler
bcd8d4b1 343 public synchronized void signalReceived(final TmfTraceModelSignal signal) {
ccc49be1
MK
344 fTraces.forEach((t, u) -> u.receive(signal));
345 }
346
347
fc526aef
AM
348 /**
349 * Handler for the TmfTraceSelectedSignal.
350 *
351 * @param signal
352 * The incoming signal
353 */
354 @TmfSignalHandler
355 public synchronized void traceSelected(final TmfTraceSelectedSignal signal) {
356 final ITmfTrace newTrace = signal.getTrace();
357 if (!fTraces.containsKey(newTrace)) {
358 throw new RuntimeException();
359 }
360 fCurrentTrace = newTrace;
361 }
362
d3de0920
XR
363 /**
364 * Signal handler for the filterApplied signal.
365 *
366 * @param signal
367 * The incoming signal
d3de0920
XR
368 */
369 @TmfSignalHandler
370 public synchronized void filterApplied(TmfEventFilterAppliedSignal signal) {
371 final ITmfTrace newTrace = signal.getTrace();
372 TmfTraceContext context = fTraces.get(newTrace);
373 if (context == null) {
374 throw new RuntimeException();
375 }
178d3c0e 376 final TmfTraceContext newContext = newTrace.createTraceContext(context.getSelectionRange(),
21852dfa
AM
377 context.getWindowRange(),
378 context.getEditorFile(),
178d3c0e
JCK
379 signal.getEventFilter());
380 newContext.setData(context.getData());
381 fTraces.put(newTrace, newContext);
d3de0920
XR
382 }
383
fc526aef
AM
384 /**
385 * Signal handler for the traceClosed signal.
386 *
387 * @param signal
388 * The incoming signal
389 */
390 @TmfSignalHandler
391 public synchronized void traceClosed(final TmfTraceClosedSignal signal) {
3fcf269e 392 fTraces.remove(signal.getTrace());
fc526aef
AM
393 if (fTraces.size() == 0) {
394 fCurrentTrace = null;
395 /*
396 * In other cases, we should receive a traceSelected signal that
397 * will indicate which trace is the new one.
398 */
399 }
400 }
401
402 /**
97c71024 403 * Signal handler for the selection range signal.
fc526aef
AM
404 *
405 * The current time of *all* traces whose range contains the requested new
0fcf3b09 406 * selection time range will be updated.
fc526aef
AM
407 *
408 * @param signal
409 * The incoming signal
97c71024 410 * @since 1.0
fc526aef
AM
411 */
412 @TmfSignalHandler
97c71024 413 public synchronized void selectionRangeUpdated(final TmfSelectionRangeUpdatedSignal signal) {
0fcf3b09
PT
414 final ITmfTimestamp beginTs = signal.getBeginTime();
415 final ITmfTimestamp endTs = signal.getEndTime();
fc526aef
AM
416
417 for (Map.Entry<ITmfTrace, TmfTraceContext> entry : fTraces.entrySet()) {
418 final ITmfTrace trace = entry.getKey();
0fcf3b09 419 if (beginTs.intersects(getValidTimeRange(trace)) || endTs.intersects(getValidTimeRange(trace))) {
21852dfa
AM
420 TmfTraceContext prevCtx = checkNotNull(entry.getValue());
421
422 /*
423 * We want to update the selection range, but keep everything
424 * else the same as the previous trace context.
425 */
426 TmfTimeRange newSelectionRange = new TmfTimeRange(beginTs, endTs);
ccc49be1 427 TmfTraceContext newCtx = trace.createTraceContext(newSelectionRange,
21852dfa
AM
428 prevCtx.getWindowRange(),
429 prevCtx.getEditorFile(),
430 prevCtx.getFilter());
178d3c0e 431 newCtx.setData(prevCtx.getData());
fc526aef
AM
432 entry.setValue(newCtx);
433 }
434 }
435 }
436
437 /**
97c71024 438 * Signal handler for the window range signal.
fc526aef 439 *
5db5a3a4
AM
440 * The current window time range of *all* valid traces will be updated to
441 * the new requested times.
fc526aef
AM
442 *
443 * @param signal
444 * The incoming signal
97c71024 445 * @since 1.0
fc526aef
AM
446 */
447 @TmfSignalHandler
97c71024 448 public synchronized void windowRangeUpdated(final TmfWindowRangeUpdatedSignal signal) {
fc526aef
AM
449 for (Map.Entry<ITmfTrace, TmfTraceContext> entry : fTraces.entrySet()) {
450 final ITmfTrace trace = entry.getKey();
21852dfa 451 final TmfTraceContext prevCtx = checkNotNull(entry.getValue());
fc526aef
AM
452
453 final TmfTimeRange validTr = getValidTimeRange(trace);
21852dfa
AM
454 if (validTr == null) {
455 return;
456 }
fc526aef 457
fc526aef 458 /* Determine the new time range */
21852dfa
AM
459 TmfTimeRange targetTr = signal.getCurrentRange().getIntersection(validTr);
460 TmfTimeRange newWindowTr = (targetTr == null ? prevCtx.getWindowRange() : targetTr);
fc526aef 461
21852dfa 462 /* Keep the values from the old context, except for the window range */
ccc49be1 463 TmfTraceContext newCtx = trace.createTraceContext(prevCtx.getSelectionRange(),
21852dfa 464 newWindowTr, prevCtx.getEditorFile(), prevCtx.getFilter());
178d3c0e 465 newCtx.setData(prevCtx.getData());
fc526aef
AM
466 entry.setValue(newCtx);
467 }
468 }
469
470 // ------------------------------------------------------------------------
e1385db9 471 // Private utility methods
fc526aef
AM
472 // ------------------------------------------------------------------------
473
474 /**
0fcf3b09
PT
475 * Return the valid time range of a trace (not the current window time
476 * range, but the range of all possible valid timestamps).
fc526aef
AM
477 *
478 * For a real trace this is the whole range of the trace. For an experiment,
479 * it goes from the start time of the earliest trace to the end time of the
480 * latest one.
481 *
482 * @param trace
483 * The trace to check for
484 * @return The valid time span, or 'null' if the trace is not valid
485 */
21852dfa 486 private @Nullable TmfTimeRange getValidTimeRange(ITmfTrace trace) {
fc526aef
AM
487 if (!fTraces.containsKey(trace)) {
488 /* Trace is not part of the currently opened traces */
489 return null;
490 }
e2239aea
BH
491
492 List<ITmfTrace> traces = trace.getChildren(ITmfTrace.class);
493
494 if (traces.isEmpty()) {
fc526aef
AM
495 /* "trace" is a single trace, return its time range directly */
496 return trace.getTimeRange();
497 }
e2239aea
BH
498
499 if (traces.size() == 1) {
fc526aef 500 /* Trace is an experiment with only 1 trace */
e2239aea 501 return traces.get(0).getTimeRange();
fc526aef 502 }
e2239aea 503
fc526aef 504 /*
e2239aea 505 * Trace is an trace set with 2+ traces, so get the earliest start and
fc526aef
AM
506 * the latest end.
507 */
e2239aea
BH
508 ITmfTimestamp start = traces.get(0).getStartTime();
509 ITmfTimestamp end = traces.get(0).getEndTime();
510
511 for (int i = 1; i < traces.size(); i++) {
512 ITmfTrace curTrace = traces.get(i);
fc526aef
AM
513 if (curTrace.getStartTime().compareTo(start) < 0) {
514 start = curTrace.getStartTime();
515 }
516 if (curTrace.getEndTime().compareTo(end) > 0) {
517 end = curTrace.getEndTime();
518 }
519 }
520 return new TmfTimeRange(start, end);
521 }
e1385db9 522
3ec38c4c
MAL
523 /**
524 * Get the temporary directory path. If there is an instance of Eclipse
525 * running, the temporary directory will reside under the workspace.
526 *
527 * @return the temporary directory path suitable to be passed to the
528 * java.io.File constructor without a trailing separator
3ec38c4c
MAL
529 */
530 public static String getTemporaryDirPath() {
531 // Get the workspace path from the properties
532 String property = System.getProperty("osgi.instance.area"); //$NON-NLS-1$
533 if (property != null) {
534 try {
1ac53e54 535 File dir = URIUtil.toFile(URIUtil.fromString(property));
3ec38c4c
MAL
536 dir = new File(dir.getAbsolutePath() + File.separator + TEMP_DIR_NAME);
537 if (!dir.exists()) {
538 dir.mkdirs();
539 }
540 return dir.getAbsolutePath();
541 } catch (URISyntaxException e) {
542 Activator.logError(e.getLocalizedMessage(), e);
543 }
544 }
545 return System.getProperty("java.io.tmpdir"); //$NON-NLS-1$
546 }
547
e1385db9 548 /**
6e4358bd
AM
549 * Get a temporary directory based on a trace's name. We will create the
550 * directory if it doesn't exist, so that it's ready to be used.
e1385db9
AM
551 */
552 private static String getTemporaryDir(ITmfTrace trace) {
3ec38c4c 553 String pathName = getTemporaryDirPath() +
fa62dc1d
BH
554 File.separator +
555 trace.getName() +
556 File.separator;
6e4358bd
AM
557 File dir = new File(pathName);
558 if (!dir.exists()) {
559 dir.mkdirs();
560 }
561 return pathName;
e1385db9 562 }
fc526aef 563}
This page took 0.114298 seconds and 5 git commands to generate.