blob: 8a2ab5201e647d23ce2903a841a57e687ab5e168 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2017 Ericsson, École Polytechnique de Montréal
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Francois Chouinard - Initial API and implementation
* Francois Chouinard - Updated as per TMF Trace Model 1.0
* Patrick Tasse - Updated for removal of context clone
* Geneviève Bastien - Added timestamp transforms, its saving to file and
* timestamp creation functions
*******************************************************************************/
package org.eclipse.tracecompass.tmf.core.trace;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.tmf.core.Activator;
import org.eclipse.tracecompass.internal.util.ByteBufferTracker;
import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModuleHelper;
import org.eclipse.tracecompass.tmf.core.analysis.TmfAnalysisManager;
import org.eclipse.tracecompass.tmf.core.component.ITmfEventProvider;
import org.eclipse.tracecompass.tmf.core.component.TmfEventProvider;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.ITmfLostEvent;
import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;
import org.eclipse.tracecompass.tmf.core.event.aspect.TmfBaseAspects;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfAnalysisException;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException;
import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.synchronization.ITmfTimestampTransform;
import org.eclipse.tracecompass.tmf.core.synchronization.TimestampTransformFactory;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.indexer.ITmfTraceIndexer;
import org.eclipse.tracecompass.tmf.core.trace.indexer.checkpoint.TmfCheckpointIndexer;
import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation;
import com.google.common.collect.ImmutableList;
/**
* Abstract implementation of ITmfTrace.
* <p>
* Since the concept of 'location' is trace specific, the concrete classes have
* to provide the related methods, namely:
* <ul>
* <li> public ITmfLocation<?> getCurrentLocation()
* <li> public double getLocationRatio(ITmfLocation<?> location)
* <li> public ITmfContext seekEvent(ITmfLocation<?> location)
* <li> public ITmfContext seekEvent(double ratio)
* <li> public IStatus validate(IProject project, String path)
* </ul>
* <p>
* When constructing an event, the concrete trace should use the trace's
* timestamp transform to create the timestamp, by either transforming the
* parsed time value directly or by using the method createTimestamp().
* <p>
* The concrete class can either specify its own indexer or use the provided
* TmfCheckpointIndexer (default). In this case, the trace cache size will be
* used as checkpoint interval.
*
* @version 1.0
* @author Francois Chouinard
*
* @see ITmfEvent
* @see ITmfTraceIndexer
* @see ITmfEventParser
*/
public abstract class TmfTrace extends TmfEventProvider implements ITmfTrace, ITmfEventParser, ITmfTraceCompleteness, IAdaptable {
// ------------------------------------------------------------------------
// Class attributes
// ------------------------------------------------------------------------
/**
* Basic aspects that should be valid for all trace types.
*/
public static final @NonNull Collection<@NonNull ITmfEventAspect<?>> BASE_ASPECTS =
ImmutableList.of(
TmfBaseAspects.getTimestampAspect(),
TmfBaseAspects.getEventTypeAspect(),
TmfBaseAspects.getContentsAspect()
);
// ------------------------------------------------------------------------
// Instance attributes
// ------------------------------------------------------------------------
// The resource used for persistent properties for this trace
private IResource fResource;
// The trace type id
private @Nullable String fTraceTypeId;
// The trace path
private String fPath;
// The trace cache page size
private int fCacheSize = ITmfTrace.DEFAULT_TRACE_CACHE_SIZE;
// The number of events collected (so far)
private volatile long fNbEvents = 0;
// The time span of the event stream
private @NonNull ITmfTimestamp fStartTime = TmfTimestamp.BIG_BANG;
private @NonNull ITmfTimestamp fEndTime = TmfTimestamp.BIG_BANG;
// The trace streaming interval (0 = no streaming)
private long fStreamingInterval = 0;
// The trace indexer
private ITmfTraceIndexer fIndexer;
private ITmfTimestampTransform fTsTransform;
private final Map<String, IAnalysisModule> fAnalysisModules =
Collections.synchronizedMap(new LinkedHashMap<String, IAnalysisModule>());
// ------------------------------------------------------------------------
// Construction
// ------------------------------------------------------------------------
/**
* The default, parameterless, constructor
*/
public TmfTrace() {
super();
fIndexer = new TmfCheckpointIndexer(this);
}
/**
* Full constructor.
*
* @param resource
* The resource associated to the trace
* @param type
* The type of events that will be read from this trace
* @param path
* The path to the trace on the filesystem
* @param cacheSize
* The trace cache size. Pass '-1' to use the default specified
* in {@link ITmfTrace#DEFAULT_TRACE_CACHE_SIZE}
* @param interval
* The trace streaming interval. You can use '0' for post-mortem
* traces.
* @throws TmfTraceException
* If something failed during the opening
*/
protected TmfTrace(final IResource resource,
final Class<? extends ITmfEvent> type,
final String path,
final int cacheSize,
final long interval)
throws TmfTraceException {
super();
fCacheSize = (cacheSize > 0) ? cacheSize : ITmfTrace.DEFAULT_TRACE_CACHE_SIZE;
fStreamingInterval = interval;
initialize(resource, path, type);
}
/**
* Copy constructor
*
* @param trace the original trace
* @throws TmfTraceException Should not happen usually
*/
public TmfTrace(final TmfTrace trace) throws TmfTraceException {
super();
if (trace == null) {
super.dispose();
throw new IllegalArgumentException();
}
fCacheSize = trace.getCacheSize();
fStreamingInterval = trace.getStreamingInterval();
initialize(trace.getResource(), trace.getPath(), trace.getEventType());
}
/**
* Creates the indexer instance. Classes extending this class can override
* this to provide a different indexer implementation.
*
* @param interval the checkpoints interval
*
* @return the indexer
*/
protected ITmfTraceIndexer createIndexer(int interval) {
return new TmfCheckpointIndexer(this, interval);
}
// ------------------------------------------------------------------------
// ITmfTrace - Initializers
// ------------------------------------------------------------------------
@Override
public void initTrace(final IResource resource, final String path, final Class<? extends ITmfEvent> type, String name, String traceTypeId) throws TmfTraceException {
if (name == null) {
throw new IllegalArgumentException();
}
setName(name);
fTraceTypeId = traceTypeId;
initTrace(resource, path, type);
}
@Override
public void initTrace(final IResource resource, final String path, final Class<? extends ITmfEvent> type) throws TmfTraceException {
initialize(resource, path, type);
}
/**
* Initialize the trace common attributes and the base component.
*
* @param resource the Eclipse resource (trace)
* @param path the trace path
* @param type the trace event type
*
* @throws TmfTraceException If something failed during the initialization
*/
protected void initialize(final IResource resource,
final String path,
final Class<? extends ITmfEvent> type)
throws TmfTraceException {
if (path == null) {
dispose();
throw new TmfTraceException("Invalid trace path"); //$NON-NLS-1$
}
fPath = path;
fResource = resource;
String traceName = getName();
if (traceName.isEmpty()) {
traceName = (resource != null) ? resource.getName() : new Path(path).lastSegment();
}
super.init(traceName, type);
// register as VIP after super.init() because TmfComponent registers to signal manager there
TmfSignalManager.registerVIP(this);
if (fIndexer != null) {
fIndexer.dispose();
}
fIndexer = createIndexer(fCacheSize);
}
/**
* Indicates if the path points to an existing file/directory
*
* @param path the path to test
* @return true if the file/directory exists
*/
protected boolean fileExists(final String path) {
final File file = new File(path);
return file.exists();
}
@Override
public void indexTrace(boolean waitForCompletion) {
getIndexer().buildIndex(0, TmfTimeRange.ETERNITY, waitForCompletion);
}
/**
* Instantiate the applicable analysis modules and executes the analysis
* modules that are meant to be automatically executed
*
* @return An IStatus indicating whether the analysis could be run
* successfully or not
*/
protected IStatus executeAnalysis() {
MultiStatus status = new MultiStatus(Activator.PLUGIN_ID, IStatus.OK, null, null);
/* First modules are initialized */
Map<String, IAnalysisModuleHelper> modules = TmfAnalysisManager.getAnalysisModules(this.getClass());
for (IAnalysisModuleHelper helper : modules.values()) {
try {
IAnalysisModule module = helper.newModule(this);
if (module == null) {
continue;
}
fAnalysisModules.put(module.getId(), module);
} catch (TmfAnalysisException e) {
status.add(new Status(IStatus.WARNING, Activator.PLUGIN_ID, e.getMessage()));
}
}
/* Once all modules are initialized, automatic modules are executed */
for (IAnalysisModule module : getAnalysisModules()) {
if (module.isAutomatic()) {
status.add(module.schedule());
}
}
return status;
}
@Override
public IAnalysisModule getAnalysisModule(String analysisId) {
return fAnalysisModules.get(analysisId);
}
@Override
public Iterable<IAnalysisModule> getAnalysisModules() {
synchronized (fAnalysisModules) {
return new HashSet<>(fAnalysisModules.values());
}
}
@Override
public Iterable<ITmfEventAspect<?>> getEventAspects() {
/* By default we provide only the base aspects valid for all trace types */
return BASE_ASPECTS;
}
/**
* Clears the trace
*/
@Override
public synchronized void dispose() {
/* Clean up the index if applicable */
if (getIndexer() != null) {
getIndexer().dispose();
}
/* Clean up the analysis modules */
Iterable<IAnalysisModule> analysisModules = getAnalysisModules();
for (IAnalysisModule module : analysisModules) {
module.dispose();
}
fAnalysisModules.clear();
super.dispose();
ByteBufferTracker.setMarked();
}
// ------------------------------------------------------------------------
// ITmfTrace - Basic getters
// ------------------------------------------------------------------------
@Override
public IResource getResource() {
return fResource;
}
@Override
public @Nullable String getTraceTypeId() {
return fTraceTypeId;
}
@Override
public String getPath() {
return fPath;
}
@Override
public int getCacheSize() {
return fCacheSize;
}
@Override
public long getStreamingInterval() {
return fStreamingInterval;
}
/**
* @return the trace indexer
*/
protected ITmfTraceIndexer getIndexer() {
return fIndexer;
}
// ------------------------------------------------------------------------
// ITmfTrace - Trace characteristics getters
// ------------------------------------------------------------------------
@Override
public long getNbEvents() {
return fNbEvents;
}
@Override
public @NonNull TmfTimeRange getTimeRange() {
return new TmfTimeRange(fStartTime, fEndTime);
}
@Override
public ITmfTimestamp getStartTime() {
return fStartTime;
}
@Override
public ITmfTimestamp getEndTime() {
return fEndTime;
}
@Override
public ITmfTimestamp getInitialRangeOffset() {
final long DEFAULT_INITIAL_OFFSET_VALUE = (1L * 100 * 1000 * 1000); // .1sec
return TmfTimestamp.fromNanos(DEFAULT_INITIAL_OFFSET_VALUE);
}
@Override
public String getHostId() {
return this.getName();
}
@Override
public boolean isIndexing() {
return fIndexer.isIndexing();
}
// ------------------------------------------------------------------------
// Convenience setters
// ------------------------------------------------------------------------
/**
* Set the trace cache size. Must be done at initialization time.
*
* @param cacheSize The trace cache size
*/
protected void setCacheSize(final int cacheSize) {
fCacheSize = cacheSize;
}
/**
* Set the trace known number of events. This can be quite dynamic
* during indexing or for live traces.
*
* @param nbEvents The number of events
*/
protected synchronized void setNbEvents(final long nbEvents) {
fNbEvents = (nbEvents > 0) ? nbEvents : 0;
}
/**
* Update the trace events time range
*
* @param range the new time range
*/
protected void setTimeRange(final @NonNull TmfTimeRange range) {
fStartTime = range.getStartTime();
fEndTime = range.getEndTime();
}
/**
* Update the trace chronologically first event timestamp
*
* @param startTime the new first event timestamp
*/
protected void setStartTime(final @NonNull ITmfTimestamp startTime) {
fStartTime = startTime;
}
/**
* Update the trace chronologically last event timestamp
*
* @param endTime the new last event timestamp
*/
protected void setEndTime(final @NonNull ITmfTimestamp endTime) {
fEndTime = endTime;
}
/**
* Set the polling interval for live traces (default = 0 = no streaming).
*
* @param interval the new trace streaming interval
*/
protected void setStreamingInterval(final long interval) {
fStreamingInterval = (interval > 0) ? interval : 0;
}
// ------------------------------------------------------------------------
// ITmfTrace - SeekEvent operations (returning a trace context)
// ------------------------------------------------------------------------
@Override
public synchronized ITmfContext seekEvent(final long rank) {
// A rank <= 0 indicates to seek the first event
if (rank <= 0) {
ITmfContext context = seekEvent((ITmfLocation) null);
context.setRank(0);
return context;
}
// Position the trace at the checkpoint
final ITmfContext context = fIndexer.seekIndex(rank);
// And locate the requested event context
long pos = context.getRank();
if (pos < rank) {
ITmfEvent event = getNext(context);
while ((event != null) && (++pos < rank)) {
event = getNext(context);
}
}
return context;
}
@Override
public synchronized ITmfContext seekEvent(final ITmfTimestamp timestamp) {
// A null timestamp indicates to seek the first event
if (timestamp == null) {
ITmfContext context = seekEvent((ITmfLocation) null);
context.setRank(0);
return context;
}
// Position the trace at the checkpoint
ITmfContext context = fIndexer.seekIndex(timestamp);
// And locate the requested event context
ITmfLocation previousLocation = context.getLocation();
long previousRank = context.getRank();
ITmfEvent event = getNext(context);
while (event != null && event.getTimestamp().compareTo(timestamp) < 0) {
previousLocation = context.getLocation();
previousRank = context.getRank();
event = getNext(context);
}
if (event == null) {
context.setLocation(null);
context.setRank(ITmfContext.UNKNOWN_RANK);
} else {
context.dispose();
context = seekEvent(previousLocation);
context.setRank(previousRank);
}
return context;
}
// ------------------------------------------------------------------------
// Read operations (returning an actual event)
// ------------------------------------------------------------------------
@Override
public abstract ITmfEvent parseEvent(ITmfContext context);
@Override
public synchronized ITmfEvent getNext(final ITmfContext context) {
// parseEvent() does not update the context
final ITmfEvent event = parseEvent(context);
if (event != null) {
updateAttributes(context, event);
context.setLocation(getCurrentLocation());
context.increaseRank();
}
return event;
}
/**
* Update the trace attributes
*
* @param context the current trace context
* @param event the corresponding event
* @since 1.1
*/
protected synchronized void updateAttributes(final ITmfContext context, final @NonNull ITmfEvent event) {
ITmfTimestamp timestamp = event.getTimestamp();
ITmfTimestamp endTime = timestamp;
if (event instanceof ITmfLostEvent) {
endTime = ((ITmfLostEvent) event).getTimeRange().getEndTime();
}
if (fStartTime.equals(TmfTimestamp.BIG_BANG) || (fStartTime.compareTo(timestamp) > 0)) {
fStartTime = timestamp;
}
if (fEndTime.equals(TmfTimestamp.BIG_CRUNCH) || (fEndTime.compareTo(endTime) < 0)) {
fEndTime = endTime;
}
if (context.hasValidRank()) {
long rank = context.getRank();
if (fNbEvents <= rank) {
fNbEvents = rank + 1;
}
if (fIndexer != null) {
fIndexer.updateIndex(context, timestamp);
}
}
}
// ------------------------------------------------------------------------
// TmfDataProvider
// ------------------------------------------------------------------------
@Override
public synchronized ITmfContext armRequest(final ITmfEventRequest request) {
if (executorIsShutdown()) {
return null;
}
if (!TmfTimestamp.BIG_BANG.equals(request.getRange().getStartTime())
&& (request.getIndex() == 0)) {
final ITmfContext context = seekEvent(request.getRange().getStartTime());
request.setStartIndex((int) context.getRank());
return context;
}
return seekEvent(request.getIndex());
}
// ------------------------------------------------------------------------
// Signal handlers
// ------------------------------------------------------------------------
/**
* Handler for the Trace Opened signal
*
* @param signal
* The incoming signal
*/
@TmfSignalHandler
public void traceOpened(TmfTraceOpenedSignal signal) {
boolean signalIsForUs = false;
ITmfEventProvider provider = this;
while (provider != null) {
if (provider == signal.getTrace()) {
signalIsForUs = true;
break;
}
provider = provider.getParent();
}
if (!signalIsForUs) {
return;
}
/*
* The signal is either for this trace, or for a parent of this trace.
*/
IStatus status = executeAnalysis();
if (!status.isOK()) {
Activator.log(status);
}
/* Refresh supplementary files in separate thread to prevent deadlock */
new Thread("Refresh supplementary files") { //$NON-NLS-1$
@Override
public void run() {
TmfTraceManager.refreshSupplementaryFiles(TmfTrace.this);
}
}.start();
if (signal.getTrace() == this) {
/* Additionally, the signal is directly for this trace. */
if (getNbEvents() == 0) {
return;
}
/* For a streaming trace, the range updated signal should be sent
* by the subclass when a new safe time is determined.
*/
if (getStreamingInterval() > 0) {
return;
}
if (isComplete()) {
final TmfTimeRange timeRange = new TmfTimeRange(getStartTime(), TmfTimestamp.BIG_CRUNCH);
final TmfTraceRangeUpdatedSignal rangeUpdatedsignal = new TmfTraceRangeUpdatedSignal(this, this, timeRange);
// Broadcast in separate thread to prevent deadlock
broadcastAsync(rangeUpdatedsignal);
}
return;
}
}
/**
* Signal handler for the TmfTraceRangeUpdatedSignal signal
*
* @param signal The incoming signal
*/
@TmfSignalHandler
public void traceRangeUpdated(final TmfTraceRangeUpdatedSignal signal) {
if (signal.getTrace() == this) {
getIndexer().buildIndex(getNbEvents(), signal.getRange(), false);
}
}
/**
* Signal handler for the TmfTraceUpdatedSignal signal
*
* @param signal The incoming signal
*/
@TmfSignalHandler
public void traceUpdated(final TmfTraceUpdatedSignal signal) {
if (signal.getSource() == getIndexer()) {
fNbEvents = signal.getNbEvents();
fStartTime = signal.getRange().getStartTime();
fEndTime = signal.getRange().getEndTime();
}
}
// ------------------------------------------------------------------------
// Timestamp transformation functions
// ------------------------------------------------------------------------
@Override
public ITmfTimestampTransform getTimestampTransform() {
if (fTsTransform == null) {
fTsTransform = TimestampTransformFactory.getTimestampTransform(getResource());
}
return fTsTransform;
}
@Override
public void setTimestampTransform(final ITmfTimestampTransform tt) {
fTsTransform = tt;
TimestampTransformFactory.setTimestampTransform(getResource(), tt);
}
@Override
public @NonNull ITmfTimestamp createTimestamp(long ts) {
return TmfTimestamp.fromNanos(getTimestampTransform().transform(ts));
}
// ------------------------------------------------------------------------
// IAdaptable
// ------------------------------------------------------------------------
//TODO: Move to ITmfTrace as default method when Bug 507246 is fixed
/**
* @since 3.0
*/
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(Class<T> adapter) {
List<T> adapters = TmfTraceAdapterManager.getAdapters(this, adapter);
if (!adapters.isEmpty()) {
return adapters.get(0);
}
if (adapter.isInstance(this)) {
return (T) this;
}
return null;
}
// ------------------------------------------------------------------------
// toString
// ------------------------------------------------------------------------
@Override
@SuppressWarnings("nls")
public synchronized String toString() {
return "TmfTrace [fPath=" + fPath + ", fCacheSize=" + fCacheSize
+ ", fNbEvents=" + fNbEvents + ", fStartTime=" + fStartTime
+ ", fEndTime=" + fEndTime + ", fStreamingInterval=" + fStreamingInterval + "]";
}
@Override
public boolean isComplete() {
/*
* Be default, all traces are "complete" which means no more data will
* be added later
*/
return true;
}
@Override
public void setComplete(boolean isComplete) {
/*
* This should be overridden by trace classes that can support live
* reading (traces in an incomplete state)
*/
}
}