/******************************************************************************* * Copyright (c) 2013, 2015 É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 org.eclipse.tracecompass.tmf.core.statesystem; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.Map; import java.util.concurrent.CountDownLatch; import org.apache.commons.io.FileUtils; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tracecompass.common.core.NonNullUtils; import org.eclipse.tracecompass.internal.tmf.core.statesystem.backends.partial.PartialHistoryBackend; import org.eclipse.tracecompass.internal.tmf.core.statesystem.backends.partial.PartialStateSystem; import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem; import org.eclipse.tracecompass.statesystem.core.ITmfStateSystemBuilder; import org.eclipse.tracecompass.statesystem.core.StateSystemFactory; import org.eclipse.tracecompass.statesystem.core.backend.IStateHistoryBackend; import org.eclipse.tracecompass.statesystem.core.backend.StateHistoryBackendFactory; import org.eclipse.tracecompass.tmf.core.analysis.TmfAbstractAnalysisModule; import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException; import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest; import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest; import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler; import org.eclipse.tracecompass.tmf.core.signal.TmfTraceRangeUpdatedSignal; import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; import org.eclipse.tracecompass.tmf.core.trace.ITmfTraceCompleteness; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils; import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment; import com.google.common.annotations.VisibleForTesting; /** * 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 */ public abstract class TmfStateSystemAnalysisModule extends TmfAbstractAnalysisModule implements ITmfAnalysisModuleWithStateSystems { private static final String EXTENSION = ".ht"; //$NON-NLS-1$ private final CountDownLatch fInitialized = new CountDownLatch(1); private final Object fRequestSyncObj = new Object(); private @Nullable ITmfStateSystemBuilder fStateSystem; private @Nullable ITmfEventRequest fRequest; private @Nullable TmfTimeRange fTimeRange = null; private int fNbRead = 0; private boolean fInitializationSucceeded; private volatile @Nullable ITmfStateProvider fStateProvider; /** * 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 } /** * Retrieve a state system belonging to trace, by passing the ID of the * relevant analysis module. * * This will start the execution of the analysis module, and start the * construction of the state system, if needed. * * @param trace * The trace for which you want the state system * @param moduleId * The ID of the state system analysis module * @return The state system, or null if there was no match or the module was * not initialized correctly */ public static @Nullable ITmfStateSystem getStateSystem(ITmfTrace trace, String moduleId) { TmfStateSystemAnalysisModule module = TmfTraceUtils.getAnalysisModuleOfClass(trace, TmfStateSystemAnalysisModule.class, moduleId); if (module != null) { ITmfStateSystem ss = module.getStateSystem(); if (ss != null) { return ss; } IStatus status = module.schedule(); if (status.isOK()) { return module.waitForInitialization() ? module.getStateSystem() : null; } } return null; } /** * 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; } /** * @since 2.0 */ @Override public boolean waitForInitialization() { try { fInitialized.await(); } catch (InterruptedException e) { return false; } return fInitializationSucceeded; } /** * @since 2.0 */ @Override public boolean isQueryable(long ts) { /* Return true if there is no state provider available (the analysis is not being built) */ ITmfStateProvider provider = fStateProvider; if (provider == null) { return true; } return ts <= provider.getLatestSafeTime(); } // ------------------------------------------------------------------------ // TmfAbstractAnalysisModule // ------------------------------------------------------------------------ /** * Get the file where to save the results of the analysis * * @return The file to save the results in * @since 2.3 */ @VisibleForTesting protected @Nullable File getSsFile() { ITmfTrace trace = getTrace(); if (trace == null) { return null; } String directory = TmfTraceManager.getSupplementaryFileDir(trace); File htFile = new File(directory + getSsFileName()); return htFile; } @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(); ITmfTrace trace = getTrace(); if (trace == null) { // Analysis was cancelled in the meantime analysisReady(false); return false; } switch (backend) { case FULL: { File htFile = getSsFile(); if (htFile == null) { return false; } createFullHistory(id, provider, htFile); } break; case PARTIAL: { File htFile = getSsFile(); if (htFile == null) { return false; } createPartialHistory(id, provider, htFile); } break; case INMEM: createInMemoryHistory(id, provider); break; case NULL: createNullHistory(id, provider); break; default: break; } } catch (TmfTraceException e) { analysisReady(false); return false; } return !mon.isCanceled(); } /** * Make the module available and set whether the initialization succeeded or * not. If not, no state system is available and * {@link #waitForInitialization()} should return false. * * @param success * True if the initialization succeeded, false otherwise */ private void analysisReady(boolean succeeded) { fInitializationSucceeded = succeeded; fInitialized.countDown(); } @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. */ private 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 = StateHistoryBackendFactory.createHistoryTreeBackendExistingFile( id, htFile, version); fStateSystem = StateSystemFactory.newStateSystem(backend, false); analysisReady(true); 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 = StateHistoryBackendFactory.createHistoryTreeBackendNewFile( id, htFile, provider.getVersion(), provider.getStartTime(), QUEUE_SIZE); fStateSystem = StateSystemFactory.newStateSystem(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 = StateHistoryBackendFactory.createHistoryTreeBackendNewFile( id, htPartialFile, provider.getVersion(), provider.getStartTime(), 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(id + ".partial", partialProvider, pss, realBackend, granularity); //$NON-NLS-1$ /* 4 */ ITmfStateSystemBuilder realSS = StateSystemFactory.newStateSystem(partialBackend); /* 5 */ pss.assignUpstream(realSS); /* 6 */ provider.assignTargetStateSystem(realSS); /* 7 */ 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 = StateHistoryBackendFactory.createNullBackend(id); fStateSystem = StateSystemFactory.newStateSystem(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 = StateHistoryBackendFactory.createInMemoryBackend(id, provider.getStartTime()); fStateSystem = StateSystemFactory.newStateSystem(backend); provider.assignTargetStateSystem(fStateSystem); build(provider); } private void disposeProvider(boolean deleteFiles) { ITmfStateProvider provider = fStateProvider; if (provider != null) { provider.dispose(); } fStateProvider = null; if (deleteFiles && (fStateSystem != null)) { fStateSystem.removeFiles(); } } private void build(ITmfStateProvider provider) { if (fStateSystem == null) { throw new IllegalArgumentException(); } ITmfEventRequest request = fRequest; if ((request != null) && (!request.isCompleted())) { request.cancel(); } fTimeRange = TmfTimeRange.ETERNITY; final ITmfTrace trace = provider.getTrace(); if (!isCompleteTrace(trace)) { fTimeRange = trace.getTimeRange(); } fStateProvider = provider; synchronized (fRequestSyncObj) { startRequest(); request = fRequest; } /* * The state system object is now created, we can consider this module * "initialized" (components can retrieve it and start doing queries). */ analysisReady(true); /* * Block the executeAnalysis() construction is complete (so that the * progress monitor displays that it is running). */ try { if (request != null) { request.waitForCompletion(); if (request.isFailed()) { Throwable failureCause = request.getFailureCause(); if (failureCause != null) { fail(failureCause); } else { fail(new RuntimeException("Event request failed without a cause")); //$NON-NLS-1$ } } } } catch (InterruptedException e) { fail(e); } } /** * A request to build a state system from a state provider * * @since 2.3 */ @VisibleForTesting protected class StateSystemEventRequest extends TmfEventRequest { private final ITmfStateProvider sci; private final ITmfTrace trace; /** * Constructor * * @param sp * The state provider used to build the state system * @param timeRange * The requested time range for the request * @param index * The event number at which to start the request */ public StateSystemEventRequest(ITmfStateProvider sp, TmfTimeRange timeRange, int index) { super(ITmfEvent.class, timeRange, index, ITmfEventRequest.ALL_DATA, ITmfEventRequest.ExecutionType.BACKGROUND, TmfStateSystemAnalysisModule.this.getDependencyLevel()); this.sci = sp; trace = sci.getTrace(); } @Override public void handleData(final ITmfEvent event) { super.handleData(event); if (event.getTrace() == trace) { sci.processEvent(event); } else if (trace instanceof TmfExperiment) { /* * If the request is for an experiment, check if the event is * from one of the child trace */ for (ITmfTrace childTrace : ((TmfExperiment) trace).getTraces()) { if (childTrace == event.getTrace()) { sci.processEvent(event); } } } } @Override public void handleSuccess() { super.handleSuccess(); if (isCompleteTrace(trace)) { disposeProvider(false); } else { fNbRead += getNbRead(); synchronized (fRequestSyncObj) { final TmfTimeRange timeRange = fTimeRange; if (timeRange != null) { if (getRange().getEndTime().getValue() < timeRange.getEndTime().getValue()) { startRequest(); } } } } } @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 @NonNull Iterable<@NonNull ITmfStateSystem> getStateSystems() { ITmfStateSystemBuilder stateSystem = fStateSystem; if (stateSystem == null) { return Collections.EMPTY_SET; } return Collections.singleton(stateSystem); } /** * Signal handler for the TmfTraceRangeUpdatedSignal signal * * @param signal The incoming signal */ @TmfSignalHandler public void traceRangeUpdated(final TmfTraceRangeUpdatedSignal signal) { fTimeRange = signal.getRange(); ITmfStateProvider stateProvider = fStateProvider; synchronized (fRequestSyncObj) { if (signal.getTrace() == getTrace() && stateProvider != null && stateProvider.getAssignedStateSystem() != null) { ITmfEventRequest request = fRequest; if ((request == null) || request.isCompleted()) { startRequest(); } } } } private void startRequest() { ITmfStateProvider stateProvider = fStateProvider; TmfTimeRange timeRange = fTimeRange; if (stateProvider == null || timeRange == null) { return; } ITmfEventRequest request = createEventRequest(stateProvider, timeRange, fNbRead); stateProvider.getTrace().sendRequest(request); fRequest = request; } /** * Create a new event request * * @param stateProvider * The state provider used to build the state system * @param timeRange * The requested time range for the request * @param nbRead * The event number at which to start the request * @return A new event request * @since 2.3 */ @VisibleForTesting protected ITmfEventRequest createEventRequest(ITmfStateProvider stateProvider, TmfTimeRange timeRange, int nbRead) { return new StateSystemEventRequest(stateProvider, timeRange, nbRead); } private static boolean isCompleteTrace(ITmfTrace trace) { return !(trace instanceof ITmfTraceCompleteness) || ((ITmfTraceCompleteness) trace).isComplete(); } // ------------------------------------------------------------------------ // ITmfPropertiesProvider // ------------------------------------------------------------------------ /** * @since 2.0 */ @Override public @NonNull Map<@NonNull String, @NonNull String> getProperties() { Map<@NonNull String, @NonNull String> properties = super.getProperties(); StateSystemBackendType backend = getBackendType(); properties.put(NonNullUtils.checkNotNull(Messages.TmfStateSystemAnalysisModule_PropertiesBackend), backend.name()); switch (backend) { case FULL: case PARTIAL: File htFile = getSsFile(); if (htFile != null) { if (htFile.exists()) { properties.put(NonNullUtils.checkNotNull(Messages.TmfStateSystemAnalysisModule_PropertiesFileSize), FileUtils.byteCountToDisplaySize(htFile.length())); } else { properties.put(NonNullUtils.checkNotNull(Messages.TmfStateSystemAnalysisModule_PropertiesFileSize), NonNullUtils.checkNotNull(Messages.TmfStateSystemAnalysisModule_PropertiesAnalysisNotExecuted)); } } break; case INMEM: case NULL: default: break; } return properties; } }