blob: d6a5eaf15ea37a4ecaf4733a393b60a0bc2c899c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2016 É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
* Alexandre Montplaisir - Initial API and implementation
* Patrick Tasse - Add message to exceptions
*******************************************************************************/
package org.eclipse.tracecompass.statesystem.core;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateValueTypeException;
import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
/**
* Provide utility methods for the state system
*
* @author Geneviève Bastien
* @author Loïc Prieur-Drevon
*/
@NonNullByDefault
public final class StateSystemUtils {
private StateSystemUtils() {
}
/**
* Convenience method to query attribute stacks (created with
* pushAttribute()/popAttribute()). This will return the interval that is
* currently at the top of the stack, or 'null' if that stack is currently
* empty. It works similarly to querySingleState().
*
* To retrieve the other values in a stack, you can query the sub-attributes
* manually.
*
* @param ss
* The state system to query
* @param t
* The timestamp of the query
* @param stackAttributeQuark
* The top-level stack-attribute (that was the target of
* pushAttribute() at creation time)
* @return The interval that was at the top of the stack, or 'null' if the
* stack was empty.
* @throws StateValueTypeException
* If the target attribute is not a valid stack attribute (if it
* has a string value for example)
* @throws AttributeNotFoundException
* If the attribute was simply not found
* @throws TimeRangeException
* If the given timestamp is invalid
* @throws StateSystemDisposedException
* If the query is sent after the state system has been disposed
*/
public static @Nullable ITmfStateInterval querySingleStackTop(ITmfStateSystem ss,
long t, int stackAttributeQuark)
throws AttributeNotFoundException, StateSystemDisposedException {
@Nullable Object curStackStateValue = ss.querySingleState(t, stackAttributeQuark).getValue();
if (curStackStateValue == null) {
/* There is nothing stored in this stack at this moment */
return null;
}
if (!(curStackStateValue instanceof Integer)) {
throw new IllegalStateException(ss.getSSID() + "Quark: " + stackAttributeQuark + ", expected Integer.class, value was " + curStackStateValue.getClass()); //$NON-NLS-1$ //$NON-NLS-2$
}
int curStackDepth = (int) curStackStateValue;
if (curStackDepth <= 0) {
/*
* This attribute is an integer attribute, but it doesn't seem like
* it's used as a stack-attribute...
*/
throw new StateValueTypeException(ss.getSSID() + " Quark:" + stackAttributeQuark + ", Stack depth:" + curStackDepth); //$NON-NLS-1$//$NON-NLS-2$
}
int subAttribQuark = ss.getQuarkRelative(stackAttributeQuark, String.valueOf(curStackDepth));
return ss.querySingleState(t, subAttribQuark);
}
/**
* Return a list of state intervals, containing the "history" of a given
* attribute between timestamps t1 and t2. The list will be ordered by
* ascending time.
*
* Note that contrary to queryFullState(), the returned list here is in the
* "direction" of time (and not in the direction of attributes, as is the
* case with queryFullState()).
*
* @param ss
* The state system to query
* @param attributeQuark
* Which attribute this query is interested in
* @param t1
* Start time of the range query
* @param t2
* Target end time of the query. If t2 is greater than the end of
* the trace, we will return what we have up to the end of the
* history.
* @return The List of state intervals that happened between t1 and t2
* @throws TimeRangeException
* If t1 is invalid, or if t2 <= t1
* @throws AttributeNotFoundException
* If the requested quark does not exist in the model.
* @throws StateSystemDisposedException
* If the query is sent after the state system has been disposed
*/
public static List<ITmfStateInterval> queryHistoryRange(ITmfStateSystem ss,
int attributeQuark, long t1, long t2)
throws AttributeNotFoundException, StateSystemDisposedException {
List<ITmfStateInterval> intervals;
ITmfStateInterval currentInterval;
long ts, tEnd;
/* Make sure the time range makes sense */
if (t2 < t1) {
throw new TimeRangeException(ss.getSSID() + " Start:" + t1 + ", End:" + t2); //$NON-NLS-1$ //$NON-NLS-2$
}
/* Set the actual, valid end time of the range query */
if (t2 > ss.getCurrentEndTime()) {
tEnd = ss.getCurrentEndTime();
} else {
tEnd = t2;
}
/* Get the initial state at time T1 */
intervals = new ArrayList<>();
currentInterval = ss.querySingleState(t1, attributeQuark);
intervals.add(currentInterval);
/* Get the following state changes */
ts = currentInterval.getEndTime();
while (ts != -1 && ts < tEnd) {
ts++; /* To "jump over" to the next state in the history */
currentInterval = ss.querySingleState(ts, attributeQuark);
intervals.add(currentInterval);
ts = currentInterval.getEndTime();
}
return intervals;
}
/**
* Return the state history of a given attribute, but with at most one
* update per "resolution". This can be useful for populating views (where
* it's useless to have more than one query per pixel, for example). A
* progress monitor can be used to cancel the query before completion.
*
* @param ss
* The state system to query
* @param attributeQuark
* Which attribute this query is interested in
* @param t1
* Start time of the range query
* @param t2
* Target end time of the query. If t2 is greater than the end of
* the trace, we will return what we have up to the end of the
* history.
* @param resolution
* The "step" of this query
* @param monitor
* A progress monitor. If the monitor is canceled during a query,
* we will return what has been found up to that point. You can
* use "null" if you do not want to use one.
* @return The List of states that happened between t1 and t2
* @throws TimeRangeException
* If t1 is invalid, if t2 <= t1, or if the resolution isn't
* greater than zero.
* @throws AttributeNotFoundException
* If the attribute doesn't exist
* @throws StateSystemDisposedException
* If the query is sent after the state system has been disposed
*/
public static List<ITmfStateInterval> queryHistoryRange(ITmfStateSystem ss,
int attributeQuark, long t1, long t2, long resolution,
@Nullable IProgressMonitor monitor)
throws AttributeNotFoundException, StateSystemDisposedException {
List<ITmfStateInterval> intervals = new LinkedList<>();
ITmfStateInterval currentInterval = null;
long ts, tEnd;
/* Make sure the time range makes sense */
if (t2 < t1 || resolution <= 0) {
throw new TimeRangeException(ss.getSSID() + " Start:" + t1 + ", End:" + t2 + ", Resolution:" + resolution); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/* Set the actual, valid end time of the range query */
if (t2 > ss.getCurrentEndTime()) {
tEnd = ss.getCurrentEndTime();
} else {
tEnd = t2;
}
IProgressMonitor mon = monitor;
if (mon == null) {
mon = new NullProgressMonitor();
}
/*
* Iterate over the "resolution points". We skip unneeded queries in the
* case the current interval is longer than the resolution.
*/
for (ts = t1; ts <= tEnd; ts += ((currentInterval.getEndTime() - ts) / resolution + 1) * resolution) {
if (mon.isCanceled()) {
return intervals;
}
currentInterval = ss.querySingleState(ts, attributeQuark);
intervals.add(currentInterval);
}
/* Add the interval at t2, if it wasn't included already. */
if (currentInterval != null && currentInterval.getEndTime() < tEnd) {
currentInterval = ss.querySingleState(tEnd, attributeQuark);
intervals.add(currentInterval);
}
return intervals;
}
/**
* Queries intervals in the state system for a given attribute, starting at
* time t1, until we obtain a non-null value.
*
* @param ss
* The state system on which to query intervals
* @param attributeQuark
* The attribute quark to query
* @param t1
* Start time of the query
* @param t2
* Time limit of the query. Use {@link Long#MAX_VALUE} for no
* limit.
* @return The first interval from t1 for which the value is not a null
* value, or <code>null</code> if no interval was found once we
* reach either t2 or the end time of the state system.
*/
public static @Nullable ITmfStateInterval queryUntilNonNullValue(ITmfStateSystem ss,
int attributeQuark, long t1, long t2) {
long current = t1;
/* Make sure the range is ok */
if (t1 < ss.getStartTime()) {
current = ss.getStartTime();
}
long end = t2;
if (end < ss.getCurrentEndTime()) {
end = ss.getCurrentEndTime();
}
/* Make sure the time range makes sense */
if (end < current) {
return null;
}
try {
while (current < t2) {
ITmfStateInterval currentInterval = ss.querySingleState(current, attributeQuark);
@Nullable Object value = currentInterval.getValue();
if (value != null) {
return currentInterval;
}
current = currentInterval.getEndTime() + 1;
}
} catch (StateSystemDisposedException | TimeRangeException e) {
/* Nothing to do */
}
return null;
}
/**
* Iterator class to allow 2-way iteration over intervals of a given
* attribute. Not thread-safe!
*
* @since 2.3
*/
public static class QuarkIterator implements Iterator<ITmfStateInterval> {
private final ITmfStateSystem fSS;
private final int fQuark;
private final long fInitialTime;
private final long fEndTime;
/**
* The last returned interval
*/
private @Nullable ITmfStateInterval fCurrent;
/**
* The previous interval (pre-fetched)
*/
private @Nullable ITmfStateInterval fPrevious;
/**
* The next interval (pre-fetched)
*/
private @Nullable ITmfStateInterval fNext;
/**
* Constructor
*
* @param ss
* The state system on which to query intervals
* @param quark
* The key to the attribute to iterate over
* @param initialTime
* The timestamp that the first returned interval will
* intersect. This timestamp can be smaller than the
* StateSystem's start time, in which case, iteration will
* start at the StateSystem's start, on bigger than the
* StateSystem's current end time, in which case iteration
* will start at the StateSystem's current end time.
* @since 2.1
*/
public QuarkIterator(ITmfStateSystem ss, int quark, long initialTime) {
this (ss, quark, initialTime, Long.MAX_VALUE);
}
/**
* Constructor
*
* @param ss
* The state system on which to query intervals
* @param quark
* The key to the attribute to iterate over
* @param initialTime
* The timestamp that the first returned interval will
* intersect. This timestamp can be smaller than the
* StateSystem's start time, in which case, iteration will
* start at the StateSystem's start, on bigger than the
* StateSystem's current end time, in which case iteration
* will start at the StateSystem's current end time.
* @param endTime
* The end of the range of intervals to iterate over, it can be
* greater than the current state system's end time, iteration
* will end at the smallest of the two.
* @throws TimeRangeException
* If endTime < initialTime.
* @since 3.2
*/
public QuarkIterator(ITmfStateSystem ss, int quark, long initialTime, long endTime) {
if (endTime < initialTime) {
throw new TimeRangeException("iterateOverQuark: end < start !"); //$NON-NLS-1$
}
fSS = ss;
fQuark = quark;
fInitialTime = initialTime;
fEndTime = endTime;
}
private long getNextQueryTime() {
if (fCurrent != null) {
return (fCurrent.getEndTime() + 1);
}
/* Iteration has not started yet */
return Long.max(fInitialTime, fSS.getStartTime());
}
private long getPreviousQueryTime() {
if (fCurrent != null) {
return fCurrent.getStartTime() - 1;
}
/* Iteration has not started yet */
return Long.min(fInitialTime, fSS.getCurrentEndTime());
}
@Override
public boolean hasNext() {
if (fNext != null) {
return true;
}
/*
* Compute the query's real end time here, to update it if the
* iterator is used during state system build
*/
long end = Long.min(fEndTime, fSS.getCurrentEndTime());
/*
* Ensure that the next query time falls within state system and
* query time range. By definition getNextQueryTime() is larger than
* the state system's start time.
*/
long nextQueryTime = getNextQueryTime();
if (nextQueryTime <= end) {
try {
fNext = fSS.querySingleState(nextQueryTime, fQuark);
} catch (StateSystemDisposedException e) {
fNext = null;
return false;
}
}
return fNext != null;
}
@Override
public ITmfStateInterval next() {
if (hasNext()) {
ITmfStateInterval next = Objects.requireNonNull(fNext, "Inconsistent state, should be non null if hasNext returned true"); //$NON-NLS-1$
fPrevious = fCurrent;
fCurrent = next;
fNext = null;
return next;
}
throw new NoSuchElementException();
}
/**
* Returns true if the iteration has more previous elements. (In other
* words, returns true if previous() would return an element rather than
* throwing an exception.)
*
* @return true if the iteration has more previous elements
*/
public boolean hasPrevious() {
if (fPrevious != null) {
return true;
}
/*
* Ensure that the next query time falls within state system and
* query time range. By definition getPreviousQueryTime() is smaller
* than the state system's end time.
*/
long previousQueryTime = getPreviousQueryTime();
if (previousQueryTime >= fSS.getStartTime()) {
try {
fPrevious = fSS.querySingleState(previousQueryTime, fQuark);
} catch (StateSystemDisposedException e) {
fPrevious = null;
return false;
}
}
return fPrevious != null;
}
/**
* Returns the previous element in the iteration.
*
* @return the previous element in the iteration
*/
public ITmfStateInterval previous() {
if (hasPrevious()) {
ITmfStateInterval prev = Objects.requireNonNull(fPrevious, "Inconsistent state, should be non null if hasPrevious returned true"); //$NON-NLS-1$
fNext = fCurrent;
fCurrent = prev;
fPrevious = null;
return prev;
}
throw new NoSuchElementException();
}
}
/**
* Build a sorted list of time stamps separated by resolution between
* bounds, including the upper bound.
*
* @param from
* lower bound of the list of time stamps.
* @param to
* upper bound of the list of time stamps.
* @param resolution
* positive duration between two consecutive time stamps.
* @return a sorted list of time stamps from start to end separated by
* resolution, or consecutive timestamps if resolution == 0.
* @throws IllegalArgumentException
* if end < start or resolution < 0.
* @since 3.0
*/
public static List<Long> getTimes(long from, long to, long resolution) {
if (to < from || resolution < 0) {
throw new IllegalArgumentException();
}
/*
* If resolution is 0, adjust increment to return consecutive
* timestamps.
*/
long increment = Math.max(resolution, 1L);
List<Long> times = new ArrayList<>((int) ((to - from) / increment + 1));
for (long t = from; t < to; t += increment) {
times.add(t);
}
times.add(to);
return times;
}
}