/*******************************************************************************
* Copyright (c) 2013, 2014 É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 v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Geneviève Bastien - Initial API and implementation
* Bernd Hufmann - Integrated history builder functionality
*******************************************************************************/
package fr.inria.linuxtools.tmf.core.statesystem;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import fr.inria.linuxtools.internal.tmf.core.statesystem.backends.partial.PartialHistoryBackend;
import fr.inria.linuxtools.internal.tmf.core.statesystem.backends.partial.PartialStateSystem;
import fr.inria.linuxtools.statesystem.core.ITmfStateSystem;
import fr.inria.linuxtools.statesystem.core.ITmfStateSystemBuilder;
import fr.inria.linuxtools.statesystem.core.StateSystemFactory;
import fr.inria.linuxtools.statesystem.core.backend.IStateHistoryBackend;
import fr.inria.linuxtools.statesystem.core.backend.InMemoryBackend;
import fr.inria.linuxtools.statesystem.core.backend.NullBackend;
import fr.inria.linuxtools.statesystem.core.backend.historytree.HistoryTreeBackend;
import fr.inria.linuxtools.statesystem.core.backend.historytree.ThreadedHistoryTreeBackend;
import fr.inria.linuxtools.tmf.core.analysis.TmfAbstractAnalysisModule;
import fr.inria.linuxtools.tmf.core.event.ITmfEvent;
import fr.inria.linuxtools.tmf.core.exceptions.TmfTraceException;
import fr.inria.linuxtools.tmf.core.request.ITmfEventRequest;
import fr.inria.linuxtools.tmf.core.request.TmfEventRequest;
import fr.inria.linuxtools.tmf.core.timestamp.TmfTimeRange;
import fr.inria.linuxtools.tmf.core.trace.ITmfTrace;
import fr.inria.linuxtools.tmf.core.trace.TmfTraceManager;
/**
* Abstract analysis module to generate a state system. It is a base class that
* can be used as a shortcut by analysis who just need to build a single state
* system with a state provider.
*
* Analysis implementing this class should only need to provide a state system
* and optionally a backend (default to NULL) and, if required, a filename
* (defaults to the analysis'ID)
*
* @author Geneviève Bastien
* @since 3.0
*/
@NonNullByDefault
public abstract class TmfStateSystemAnalysisModule extends TmfAbstractAnalysisModule
implements ITmfAnalysisModuleWithStateSystems {
private static final String EXTENSION = ".ht"; //$NON-NLS-1$
private final CountDownLatch fInitialized = new CountDownLatch(1);
@Nullable private ITmfStateSystemBuilder fStateSystem;
@Nullable private ITmfStateProvider fStateProvider;
@Nullable private IStateHistoryBackend fHtBackend;
@Nullable private ITmfEventRequest fRequest;
/**
* State system backend types
*
* @author Geneviève Bastien
*/
protected enum StateSystemBackendType {
/** Full history in file */
FULL,
/** In memory state system */
INMEM,
/** Null history */
NULL,
/** State system backed with partial history */
PARTIAL
}
/**
* Get the state provider for this analysis module
*
* @return the state provider
*/
protected abstract ITmfStateProvider createStateProvider();
/**
* Get the state system backend type used by this module
*
* @return The {@link StateSystemBackendType}
*/
protected StateSystemBackendType getBackendType() {
/* Using full history by default, sub-classes can override */
return StateSystemBackendType.FULL;
}
/**
* Get the supplementary file name where to save this state system. The
* default is the ID of the analysis followed by the extension.
*
* @return The supplementary file name
*/
protected String getSsFileName() {
return getId() + EXTENSION;
}
/**
* Get the state system generated by this analysis, or null if it is not yet
* created.
*
* @return The state system
*/
@Nullable
public ITmfStateSystem getStateSystem() {
return fStateSystem;
}
/**
* Block the calling thread until the analysis module has been initialized.
* After this method returns, {@link #getStateSystem()} should not return
* null anymore.
*/
public void waitForInitialization() {
try {
fInitialized.await();
} catch (InterruptedException e) {}
}
// ------------------------------------------------------------------------
// TmfAbstractAnalysisModule
// ------------------------------------------------------------------------
@Override
protected boolean executeAnalysis(@Nullable final IProgressMonitor monitor) {
IProgressMonitor mon = (monitor == null ? new NullProgressMonitor() : monitor);
final ITmfStateProvider provider = createStateProvider();
String id = getId();
/* FIXME: State systems should make use of the monitor, to be cancelled */
try {
/* Get the state system according to backend */
StateSystemBackendType backend = getBackendType();
String directory;
File htFile;
switch (backend) {
case FULL:
directory = TmfTraceManager.getSupplementaryFileDir(getTrace());
htFile = new File(directory + getSsFileName());
createFullHistory(id, provider, htFile);
break;
case PARTIAL:
directory = TmfTraceManager.getSupplementaryFileDir(getTrace());
htFile = new File(directory + getSsFileName());
createPartialHistory(id, provider, htFile);
break;
case INMEM:
createInMemoryHistory(id, provider);
break;
case NULL:
createNullHistory(id, provider);
break;
default:
break;
}
} catch (TmfTraceException e) {
return false;
}
return !mon.isCanceled();
}
@Override
protected void canceling() {
ITmfEventRequest req = fRequest;
if ((req != null) && (!req.isCompleted())) {
req.cancel();
}
}
@Override
public void dispose() {
super.dispose();
if (fStateSystem != null) {
fStateSystem.dispose();
}
}
// ------------------------------------------------------------------------
// History creation methods
// ------------------------------------------------------------------------
/**
* Load the history file matching the target trace. If the file already
* exists, it will be opened directly. If not, it will be created from
* scratch.
*
* @param id
* The ID of the statesystem. It should be unique
* @param provider
* The state provider
* @param htFile
* Filename/location of the history we want to load
* @throws TmfTraceException
* when an erros occurs while opening the file
* @Framesoc
*/
protected void createFullHistory(String id, ITmfStateProvider provider, File htFile) throws TmfTraceException {
/* If the target file already exists, do not rebuild it uselessly */
// TODO for now we assume it's complete. Might be a good idea to check
// at least if its range matches the trace's range.
if (htFile.exists()) {
/* Load an existing history */
final int version = provider.getVersion();
try {
IStateHistoryBackend backend = new HistoryTreeBackend(htFile, version);
fHtBackend = backend;
fStateSystem = StateSystemFactory.newStateSystem(id, backend, false);
fInitialized.countDown();
return;
} catch (IOException e) {
/*
* There was an error opening the existing file. Perhaps it was
* corrupted, perhaps it's an old version? We'll just
* fall-through and try to build a new one from scratch instead.
*/
}
}
/* Size of the blocking queue to use when building a state history */
final int QUEUE_SIZE = 10000;
try {
IStateHistoryBackend backend = new ThreadedHistoryTreeBackend(htFile,
provider.getStartTime(), provider.getVersion(), QUEUE_SIZE);
fHtBackend = backend;
fStateSystem = StateSystemFactory.newStateSystem(id, backend);
provider.assignTargetStateSystem(fStateSystem);
build(provider);
} catch (IOException e) {
/*
* If it fails here however, it means there was a problem writing to
* the disk, so throw a real exception this time.
*/
throw new TmfTraceException(e.toString(), e);
}
}
/*
* Create a new state system backed with a partial history. A partial
* history is similar to a "full" one (which you get with
* {@link #newFullHistory}), except that the file on disk is much smaller,
* but queries are a bit slower.
*
* Also note that single-queries are implemented using a full-query
* underneath, (which are much slower), so this might not be a good fit for
* a use case where you have to do lots of single queries.
*/
private void createPartialHistory(String id, ITmfStateProvider provider, File htPartialFile)
throws TmfTraceException {
/*
* The order of initializations is very tricky (but very important!)
* here. We need to follow this pattern:
* (1 is done before the call to this method)
*
* 1- Instantiate realStateProvider
* 2- Instantiate realBackend
* 3- Instantiate partialBackend, with prereqs:
* 3a- Instantiate partialProvider, via realProvider.getNew()
* 3b- Instantiate nullBackend (partialSS's backend)
* 3c- Instantiate partialSS
* 3d- partialProvider.assignSS(partialSS)
* 4- Instantiate realSS
* 5- partialSS.assignUpstream(realSS)
* 6- realProvider.assignSS(realSS)
* 7- Call HistoryBuilder(realProvider, realSS, partialBackend) to build the thing.
*/
/* Size of the blocking queue to use when building a state history */
final int QUEUE_SIZE = 10000;
final long granularity = 50000;
/* 2 */
IStateHistoryBackend realBackend = null;
try {
realBackend = new ThreadedHistoryTreeBackend(htPartialFile,
provider.getStartTime(), provider.getVersion(), QUEUE_SIZE);
} catch (IOException e) {
throw new TmfTraceException(e.toString(), e);
}
/* 3a */
ITmfStateProvider partialProvider = provider.getNewInstance();
/* 3b-3c, constructor automatically uses a NullBackend */
PartialStateSystem pss = new PartialStateSystem();
/* 3d */
partialProvider.assignTargetStateSystem(pss);
/* 3 */
IStateHistoryBackend partialBackend =
new PartialHistoryBackend(partialProvider, pss, realBackend, granularity);
/* 4 */
@SuppressWarnings("restriction")
fr.inria.linuxtools.internal.statesystem.core.StateSystem realSS =
(fr.inria.linuxtools.internal.statesystem.core.StateSystem) StateSystemFactory.newStateSystem(id, partialBackend);
/* 5 */
pss.assignUpstream(realSS);
/* 6 */
provider.assignTargetStateSystem(realSS);
/* 7 */
fHtBackend = partialBackend;
fStateSystem = realSS;
build(provider);
}
/*
* Create a new state system using a null history back-end. This means that
* no history intervals will be saved anywhere, and as such only
* {@link ITmfStateSystem#queryOngoingState} will be available.
*/
private void createNullHistory(String id, ITmfStateProvider provider) {
IStateHistoryBackend backend = new NullBackend();
fHtBackend = backend;
fStateSystem = StateSystemFactory.newStateSystem(id, backend);
provider.assignTargetStateSystem(fStateSystem);
build(provider);
}
/*
* Create a new state system using in-memory interval storage. This should
* only be done for very small state system, and will be naturally limited
* to 2^31 intervals.
*/
private void createInMemoryHistory(String id, ITmfStateProvider provider) {
IStateHistoryBackend backend = new InMemoryBackend(provider.getStartTime());
fHtBackend = backend;
fStateSystem = StateSystemFactory.newStateSystem(id, backend);
provider.assignTargetStateSystem(fStateSystem);
build(provider);
}
private void disposeProvider(boolean deleteFiles) {
ITmfStateProvider provider = fStateProvider;
if (provider != null) {
provider.dispose();
}
if (deleteFiles && (fHtBackend != null)) {
fHtBackend.removeFiles();
}
}
private void build(ITmfStateProvider provider) {
if ((fStateSystem == null) || (fHtBackend == null)) {
throw new IllegalArgumentException();
}
ITmfEventRequest request = fRequest;
if ((request != null) && (!request.isCompleted())) {
request.cancel();
}
request = new StateSystemEventRequest(provider);
provider.getTrace().sendRequest(request);
/*
* Only now that we've actually started the build, we'll update the
* class fields, so that they become visible for other callers.
*/
fStateProvider = provider;
fRequest = request;
/*
* The state system object is now created, we can consider this module
* "initialized" (components can retrieve it and start doing queries).
*/
fInitialized.countDown();
/*
* Block the executeAnalysis() construction is complete (so that the
* progress monitor displays that it is running).
*/
try {
request.waitForCompletion();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private class StateSystemEventRequest extends TmfEventRequest {
private final ITmfStateProvider sci;
private final ITmfTrace trace;
public StateSystemEventRequest(ITmfStateProvider sp) {
super(sp.getExpectedEventType(),
TmfTimeRange.ETERNITY,
0,
ITmfEventRequest.ALL_DATA,
ITmfEventRequest.ExecutionType.BACKGROUND);
this.sci = sp;
// sci.getTrace() will eventually return a @NonNull
@SuppressWarnings("null")
@NonNull ITmfTrace tr = sci.getTrace();
this.trace = tr;
}
@Override
public void handleData(final ITmfEvent event) {
super.handleData(event);
if (event.getTrace() == trace) {
sci.processEvent(event);
}
}
@Override
public void handleSuccess() {
super.handleSuccess();
disposeProvider(false);
}
@Override
public void handleCancel() {
super.handleCancel();
disposeProvider(true);
}
@Override
public void handleFailure() {
super.handleFailure();
disposeProvider(true);
}
}
// ------------------------------------------------------------------------
// ITmfAnalysisModuleWithStateSystems
// ------------------------------------------------------------------------
@Override
@Nullable
public ITmfStateSystem getStateSystem(String id) {
if (id.equals(getId())) {
return fStateSystem;
}
return null;
}
@Override
public Iterable<ITmfStateSystem> getStateSystems() {
@SuppressWarnings("null")
@NonNull Iterable<ITmfStateSystem> ret = Collections.singleton((ITmfStateSystem) fStateSystem);
return ret;
}
}