/*******************************************************************************
* 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
* Matthew Khouzam - Modified to use a TreeSet
* Patrick Tasse - Add message to exceptions
******************************************************************************/
package org.eclipse.tracecompass.internal.statesystem.core.backend;
import java.io.File;
import java.io.FileInputStream;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import java.util.SortedSet;
import java.util.TreeSet;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.statesystem.core.backend.IStateHistoryBackend;
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.statesystem.core.statevalue.TmfStateValue;
/**
* State history back-end that stores its intervals in RAM only. It cannot be
* saved to disk, which means we need to rebuild it every time we re-open a
* trace. But it's relatively quick to build, so this shouldn't be a problem in
* most cases.
*
* This should only be used with very small state histories (and/or, very small
* traces). Since it's stored in standard Collections, it's limited to 2^31
* intervals.
*
* @author Alexandre Montplaisir
*/
public class InMemoryBackend implements IStateHistoryBackend {
/**
* We need to compare the end time and the attribute, because we can have 2
* intervals with the same end time (for different attributes). And TreeSet
* needs a unique "key" per element.
*/
private static final Comparator<ITmfStateInterval> END_COMPARATOR =
new Comparator<ITmfStateInterval>() {
@Override
public int compare(ITmfStateInterval o1, ITmfStateInterval o2) {
final long e1 = o1.getEndTime();
final long e2 = o2.getEndTime();
final int a1 = o1.getAttribute();
final int a2 = o2.getAttribute();
if (e1 < e2) {
return -1;
} else if (e1 > e2) {
return 1;
} else if (a1 < a2) {
return -1;
} else if (a1 > a2) {
return 1;
} else {
return 0;
}
}
};
private final @NonNull String ssid;
private final NavigableSet<ITmfStateInterval> intervals;
private final long startTime;
private volatile long latestTime;
/**
* Constructor
*
* @param ssid
* The state system's ID
* @param startTime
* The start time of this interval store
*/
public InMemoryBackend(@NonNull String ssid, long startTime) {
this.ssid = ssid;
this.startTime = startTime;
this.latestTime = startTime;
this.intervals = new TreeSet<>(END_COMPARATOR);
}
@Override
public String getSSID() {
return ssid;
}
@Override
public long getStartTime() {
return startTime;
}
@Override
public long getEndTime() {
return latestTime;
}
@Override
public void insertPastState(long stateStartTime, long stateEndTime,
int quark, ITmfStateValue value) throws TimeRangeException {
/* Make sure the passed start/end times make sense */
if (stateStartTime > stateEndTime || stateStartTime < startTime) {
throw new TimeRangeException(ssid + " Interval Start:" + stateStartTime + ", Interval End:" + stateEndTime + ", Backend Start:" + startTime); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
ITmfStateInterval interval = new TmfStateInterval(stateStartTime, stateEndTime, quark, value);
/* Add the interval into the tree */
synchronized (intervals) {
intervals.add(interval);
}
/* Update the "latest seen time" */
if (stateEndTime > latestTime) {
latestTime = stateEndTime;
}
}
@Override
public void doQuery(List<ITmfStateInterval> currentStateInfo, long t)
throws TimeRangeException {
if (!checkValidTime(t)) {
throw new TimeRangeException(ssid + " Time:" + t + ", Start:" + startTime + ", End:" + latestTime); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/*
* The intervals are sorted by end time, so we can binary search to get
* the first possible interval, then only compare their start times.
*/
synchronized (intervals) {
Iterator<ITmfStateInterval> iter = searchforEndTime(intervals, t);
for (int modCount = 0; iter.hasNext() && modCount < currentStateInfo.size();) {
ITmfStateInterval entry = iter.next();
final long entryStartTime = entry.getStartTime();
if (entryStartTime <= t) {
/* Add this interval to the returned values */
currentStateInfo.set(entry.getAttribute(), entry);
modCount++;
}
}
}
}
@Override
public ITmfStateInterval doSingularQuery(long t, int attributeQuark)
throws TimeRangeException {
if (!checkValidTime(t)) {
throw new TimeRangeException(ssid + " Time:" + t + ", Start:" + startTime + ", End:" + latestTime); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/*
* The intervals are sorted by end time, so we can binary search to get
* the first possible interval, then only compare their start times.
*/
synchronized (intervals) {
Iterator<ITmfStateInterval> iter = searchforEndTime(intervals, t);
while (iter.hasNext()) {
ITmfStateInterval entry = iter.next();
final boolean attributeMatches = (entry.getAttribute() == attributeQuark);
final long entryStartTime = entry.getStartTime();
if (attributeMatches) {
if (entryStartTime <= t) {
/* This is the droid we are looking for */
return entry;
}
}
}
}
return null;
}
private boolean checkValidTime(long t) {
if (t >= startTime && t <= latestTime) {
return true;
}
return false;
}
@Override
public void finishedBuilding(long endTime) throws TimeRangeException {
/* Nothing to do */
}
@Override
public FileInputStream supplyAttributeTreeReader() {
/* Saving to disk not supported */
return null;
}
@Override
public File supplyAttributeTreeWriterFile() {
/* Saving to disk not supported */
return null;
}
@Override
public long supplyAttributeTreeWriterFilePosition() {
/* Saving to disk not supported */
return -1;
}
@Override
public void removeFiles() {
/* Nothing to do */
}
@Override
public void dispose() {
/* Nothing to do */
}
private static Iterator<ITmfStateInterval> searchforEndTime(NavigableSet<ITmfStateInterval> tree, long time) {
ITmfStateInterval dummyInterval = new TmfStateInterval(-1, time, -1, TmfStateValue.nullValue());
ITmfStateInterval myInterval = tree.lower(dummyInterval);
if (myInterval == null) {
return tree.iterator();
}
final SortedSet<ITmfStateInterval> tailSet = tree.tailSet(myInterval);
Iterator<ITmfStateInterval> retVal = tailSet.iterator();
retVal.next();
return retVal;
}
}