/*******************************************************************************
* Copyright (c) 2013, 2016 Ericsson
* 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:
* Alexandre Montplaisir - Initial API and implementation
* Patrick Tasse - Add message to exceptions
*******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.core.statesystem.backends.partial;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNullContents;
import java.io.File;
import java.io.FileInputStream;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.backend.IStateHistoryBackend;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.statesystem.core.interval.TmfStateInterval;
import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest;
import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest;
import org.eclipse.tracecompass.tmf.core.statesystem.AbstractTmfStateProvider;
import org.eclipse.tracecompass.tmf.core.statesystem.ITmfStateProvider;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
/**
* Partial state history back-end.
*
* This is a shim inserted between the real state system and a "real" history
* back-end. It will keep checkpoints, every n trace events (where n is called
* the granularity) and will only forward to the real state history the state
* intervals that crosses at least one checkpoint. Every other interval will
* be discarded.
*
* This would mean that it can only answer queries exactly at the checkpoints.
* For any other timestamps (ie, most of the time), it will load the closest
* earlier checkpoint, and will re-feed the state-change-input with events from
* the trace, to restore the real state at the time that was requested.
*
* @author Alexandre Montplaisir
*/
public class PartialHistoryBackend implements IStateHistoryBackend {
private final @NonNull String fSSID;
/**
* A partial history needs the state input plugin to re-generate state
* between checkpoints.
*/
private final @NonNull ITmfStateProvider fPartialInput;
/**
* Fake state system that is used for partially rebuilding the states (when
* going from a checkpoint to a target query timestamp).
*/
private final @NonNull PartialStateSystem fPartialSS;
/** Reference to the "real" state history that is used for storage */
private final @NonNull IStateHistoryBackend fInnerHistory;
/** Checkpoints map, <Timestamp, Rank in the trace> */
private final @NonNull TreeMap<Long, Long> fCheckpoints = new TreeMap<>();
/** Latch tracking if the initial checkpoint registration is done */
private final @NonNull CountDownLatch fCheckpointsReady = new CountDownLatch(1);
private final long fGranularity;
private long fLatestTime;
/**
* Constructor
*
* @param ssid
* The state system's ID
* @param partialInput
* The state change input object that was used to build the
* upstream state system. This partial history will make its own
* copy (since they have different targets).
* @param pss
* The partial history's inner state system. It should already be
* assigned to partialInput.
* @param realBackend
* The real state history back-end to use. It's supposed to be
* modular, so it should be able to be of any type.
* @param granularity
* Configuration parameter indicating how many trace events there
* should be between each checkpoint
*/
public PartialHistoryBackend(@NonNull String ssid,
ITmfStateProvider partialInput,
PartialStateSystem pss,
IStateHistoryBackend realBackend,
long granularity) {
if (granularity <= 0 || partialInput == null || pss == null ||
partialInput.getAssignedStateSystem() != pss) {
throw new IllegalArgumentException();
}
final long startTime = realBackend.getStartTime();
fSSID = ssid;
fPartialInput = partialInput;
fPartialSS = pss;
fInnerHistory = realBackend;
fGranularity = granularity;
fLatestTime = startTime;
registerCheckpoints();
}
private void registerCheckpoints() {
ITmfEventRequest request = new CheckpointsRequest(fPartialInput, fCheckpoints);
fPartialInput.getTrace().sendRequest(request);
/* The request will countDown the checkpoints latch once it's finished */
}
@Override
public String getSSID() {
return fSSID;
}
@Override
public long getStartTime() {
return fInnerHistory.getStartTime();
}
@Override
public long getEndTime() {
return fLatestTime;
}
@Override
public void insertPastState(long stateStartTime, long stateEndTime,
int quark, ITmfStateValue value) throws TimeRangeException {
waitForCheckpoints();
/* Update the latest time */
if (stateEndTime > fLatestTime) {
fLatestTime = stateEndTime;
}
/*
* Check if the interval intersects the previous checkpoint. If so,
* insert it in the real history back-end.
*
* FIXME since intervals are inserted in order of rank, we could avoid
* doing a map lookup every time here (just compare with the known
* previous one).
*/
if (stateStartTime <= fCheckpoints.floorKey(stateEndTime)) {
fInnerHistory.insertPastState(stateStartTime, stateEndTime, quark, value);
}
}
@Override
public void finishedBuilding(long endTime) throws TimeRangeException {
fInnerHistory.finishedBuilding(endTime);
}
@Override
public FileInputStream supplyAttributeTreeReader() {
return fInnerHistory.supplyAttributeTreeReader();
}
@Override
public File supplyAttributeTreeWriterFile() {
return fInnerHistory.supplyAttributeTreeWriterFile();
}
@Override
public long supplyAttributeTreeWriterFilePosition() {
return fInnerHistory.supplyAttributeTreeWriterFilePosition();
}
@Override
public void removeFiles() {
fInnerHistory.removeFiles();
}
@Override
public void dispose() {
fPartialInput.dispose();
fPartialSS.dispose();
fInnerHistory.dispose();
}
@Override
public void doQuery(List<@Nullable ITmfStateInterval> currentStateInfo, long t)
throws TimeRangeException, StateSystemDisposedException {
/* Wait for required steps to be done */
waitForCheckpoints();
fPartialSS.getUpstreamSS().waitUntilBuilt();
if (!checkValidTime(t)) {
throw new TimeRangeException(fSSID + " Time:" + t + ", Start:" + getStartTime() + ", End:" + getEndTime()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/* Reload the previous checkpoint */
long checkpointTime = fCheckpoints.floorKey(t);
fInnerHistory.doQuery(currentStateInfo, checkpointTime);
/*
* Set the initial contents of the partial state system (which is the
* contents of the query at the checkpoint).
*/
List<@NonNull ITmfStateInterval> filledStateInfo =
checkNotNullContents(currentStateInfo.stream()).collect(Collectors.toList());
fPartialSS.takeQueryLock();
fPartialSS.replaceOngoingState(filledStateInfo);
/* Send an event request to update the state system to the target time. */
TmfTimeRange range = new TmfTimeRange(
/*
* The state at the checkpoint already includes any state change
* caused by the event(s) happening exactly at 'checkpointTime',
* if any. We must not include those events in the query.
*/
TmfTimestamp.fromNanos(checkpointTime + 1),
TmfTimestamp.fromNanos(t));
ITmfEventRequest request = new PartialStateSystemRequest(fPartialInput, range);
fPartialInput.getTrace().sendRequest(request);
try {
request.waitForCompletion();
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
* Now the partial state system should have the ongoing time we are
* looking for. However, the method expects a List of *state intervals*,
* not state values, so we'll create intervals with a dummy end time.
*/
for (int i = 0; i < currentStateInfo.size(); i++) {
long start = 0;
start = ((ITmfStateSystem) fPartialSS).getOngoingStartTime(i);
ITmfStateValue val = ((ITmfStateSystem) fPartialSS).queryOngoingState(i);
ITmfStateInterval interval = new TmfStateInterval(start, t, i, checkNotNull(val));
currentStateInfo.set(i, interval);
}
fPartialSS.releaseQueryLock();
}
/**
* Single queries are not supported in partial histories. To get the same
* result you can do a full query, then call fullState.get(attribute).
*/
@Override
public ITmfStateInterval doSingularQuery(long t, int attributeQuark) {
throw new UnsupportedOperationException();
}
private boolean checkValidTime(long t) {
return (t >= getStartTime() && t <= getEndTime());
}
private void waitForCheckpoints() {
try {
fCheckpointsReady.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// ------------------------------------------------------------------------
// Event requests types
// ------------------------------------------------------------------------
private class CheckpointsRequest extends TmfEventRequest {
private final ITmfTrace trace;
private final Map<Long, Long> checkpts;
private long eventCount;
private long lastCheckpointAt;
public CheckpointsRequest(ITmfStateProvider input, Map<Long, Long> checkpoints) {
super(ITmfEvent.class,
TmfTimeRange.ETERNITY,
0,
ITmfEventRequest.ALL_DATA,
ITmfEventRequest.ExecutionType.FOREGROUND);
checkpoints.clear();
this.trace = input.getTrace();
this.checkpts = checkpoints;
eventCount = 0;
lastCheckpointAt = 0;
/* Insert a checkpoint at the start of the trace */
checkpoints.put(input.getStartTime(), 0L);
}
@Override
public void handleData(final ITmfEvent event) {
super.handleData(event);
if (event.getTrace() == trace) {
eventCount++;
/* Check if we need to register a new checkpoint */
if (eventCount >= lastCheckpointAt + fGranularity) {
checkpts.put(event.getTimestamp().getValue(), eventCount);
lastCheckpointAt = eventCount;
}
}
}
@Override
public void handleCompleted() {
super.handleCompleted();
fCheckpointsReady.countDown();
}
}
private class PartialStateSystemRequest extends TmfEventRequest {
private final ITmfStateProvider sci;
private final ITmfTrace trace;
PartialStateSystemRequest(ITmfStateProvider sci, TmfTimeRange range) {
super(ITmfEvent.class,
range,
0,
ITmfEventRequest.ALL_DATA,
ITmfEventRequest.ExecutionType.BACKGROUND);
this.sci = sci;
this.trace = sci.getTrace();
}
@Override
public void handleData(final ITmfEvent event) {
super.handleData(event);
if (event.getTrace() == trace) {
sci.processEvent(event);
}
}
@Override
public void handleCompleted() {
/*
* If we're using a threaded state provider, we need to make sure
* all events have been handled by the state system before doing
* queries on it.
*/
if (fPartialInput instanceof AbstractTmfStateProvider) {
((AbstractTmfStateProvider) fPartialInput).waitForEmptyQueue();
}
super.handleCompleted();
}
}
}