/**
*
*/
package fr.inria.soctrace.framesoc.ui.piechart.loaders;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fr.inria.soctrace.framesoc.ui.colors.FramesocColor;
import fr.inria.soctrace.framesoc.ui.colors.FramesocColorManager;
import fr.inria.soctrace.framesoc.ui.model.TimeInterval;
import fr.inria.soctrace.framesoc.ui.piechart.model.PieChartLoaderMap;
import fr.inria.soctrace.lib.model.EventType;
import fr.inria.soctrace.lib.model.Trace;
import fr.inria.soctrace.lib.model.utils.SoCTraceException;
import fr.inria.soctrace.lib.model.utils.TimestampFormat;
import fr.inria.soctrace.lib.model.utils.ModelConstants.TimeUnit;
import fr.inria.soctrace.lib.query.EventTypeQuery;
import fr.inria.soctrace.lib.query.conditions.ConditionsConstants.ComparisonOperation;
import fr.inria.soctrace.lib.query.conditions.SimpleCondition;
import fr.inria.soctrace.lib.storage.DBObject;
import fr.inria.soctrace.lib.storage.DBObject.DBMode;
import fr.inria.soctrace.lib.storage.TraceDBObject;
import fr.inria.soctrace.lib.utils.DeltaManager;
/**
* Base abstract class for Pie Chart loaders dealing with duration (e.g., state duration).
*
* @author "Generoso Pagano <generoso.pagano@inria.fr>"
*/
public abstract class DurationPieChartLoader extends EventPieChartLoader {
/**
* Logger
*/
private final static Logger logger = LoggerFactory.getLogger(DurationPieChartLoader.class);
/**
* Event type map for the duration category managed: id -> name
*/
private Map<Integer, String> etMap;
/**
* Formatter
*/
private TimestampFormat formatter = new TimestampFormat();
/**
* Utility class for a pending duration.
*
* A pending duration is a portion of an entity with a duration (e.g., state) that has been
* already read from DB, but not completely added yet to current statistics. For example
* consider the following situation: we have read a state starting at 0 and ending at 5, but
* doRequest() has been called only for the interval (1,3). This generates two pending
* durations: (0,1) and (3,5).
*/
private static class PendingDuration {
public int typeId;
public long start;
public long end;
public PendingDuration(int type) {
typeId = type;
}
@Override
public String toString() {
return "PendingDuration [typeId=" + typeId + ", start=" + start + ", end=" + end + "]";
}
}
/**
* List of pending durations for the current load operation.
*/
private List<PendingDuration> pending;
/**
* Get the event category to use.
*
* It must be a category that implies the concept of duration, i.e., states or links. The
* category is returned as one of the integer constant in <code>EventCategory</code>.
*
* @return the duration category to use
*/
protected abstract int getDurationCategory();
@Override
protected FramesocColor getBaseColor(String name) {
FramesocColor color = FramesocColorManager.getInstance().getEventTypeColor(name);
FramesocColorManager.getInstance().saveEventTypeColors();
return color;
}
@Override
public NumberFormat getFormat() {
formatter.setTimeUnit(TimeUnit.UNKNOWN);
return formatter;
}
@Override
public void load(Trace trace, TimeInterval requestedInterval, PieChartLoaderMap map,
IProgressMonitor monitor) {
if (trace == null || requestedInterval == null || map == null || monitor == null)
throw new NullPointerException();
TraceDBObject traceDB = null;
try {
// reset pending
pending = new LinkedList<>();
DeltaManager dm = new DeltaManager();
dm.start();
traceDB = new TraceDBObject(trace.getDbName(), DBMode.DB_OPEN);
// lazily load the type map
loadEventTypeMap(traceDB);
// compute interval duration
long duration = trace.getMaxTimestamp() - trace.getMinTimestamp();
Assert.isTrue(duration != 0, "The trace duration cannot be 0");
double density = ((double) trace.getNumberOfEvents()) / duration;
Assert.isTrue(density != 0, "The density cannot be 0");
long intervalDuration = (long) (EVENTS_PER_QUERY / density);
Assert.isTrue(intervalDuration > 0, "The interval duration must be positive");
Map<String, Double> values = new HashMap<>();
long t0 = requestedInterval.startTimestamp;
TimeInterval loadedInterval = new TimeInterval(t0, 0);
// if we are loading the metric from the beginning of the trace, the
// first interval is
// not different from the others
boolean first = (t0 != trace.getMinTimestamp());
while (t0 < requestedInterval.endTimestamp) {
if (checkCancel(map, monitor)) {
return;
}
// load interval
long t1 = Math.min(requestedInterval.endTimestamp, t0 + intervalDuration);
boolean last = (t1 >= requestedInterval.endTimestamp);
int results = doRequest(t0, t1, first, last, values, traceDB, monitor);
first = false;
logger.debug("Loaded: " + results);
if (checkCancel(map, monitor)) {
return;
}
// check for empty regions
if (results == 0 && !last) {
t0 = getNextTimestampStartingFrom(traceDB, t1);
// if we skip and interval, manage the pending durations for
// the skipped parts
managePendingDurations(t1, t0, values);
logger.debug("saved " + ((t0 - t1) / intervalDuration) + " queries.");
continue;
}
loadedInterval.endTimestamp = t1;
map.setSnapshot(values, loadedInterval);
// use same time unit for value displayed in the table
formatter.setContext(loadedInterval.startTimestamp, loadedInterval.endTimestamp);
t0 = t1;
}
map.setComplete();
logger.debug(dm.endMessage("Prepared Pie Chart dataset"));
} catch (SoCTraceException e) {
e.printStackTrace();
map.setStop();
} finally {
if (!map.isStop() && !map.isComplete()) {
// something went wrong, respect the map contract anyway
map.setStop();
}
DBObject.finalClose(traceDB);
}
}
@Override
protected int doRequest(long t0, long t1, boolean first, boolean last,
Map<String, Double> values, TraceDBObject traceDB, IProgressMonitor monitor)
throws SoCTraceException {
logger.debug("do request: {}, {}. First: {}, Last: {}.", t0, t1, first, last);
logger.debug("before managing pending");
logger.debug(pending.toString());
// manage pending
managePendingDurations(t0, t1, values);
logger.debug("after managing pending");
logger.debug(pending.toString());
// execute query
ComparisonOperation lastComp = last ? ComparisonOperation.LE : ComparisonOperation.LT;
StringBuilder query = new StringBuilder("SELECT EVENT_TYPE_ID, TIMESTAMP, LPAR FROM EVENT");
query.append(" WHERE ");
query.append("CATEGORY = ").append(getDurationCategory());
query.append(" AND ");
if (first) {
// start <(=) t1 && end >= t0
query.append("TIMESTAMP " + lastComp + t1 + " AND LPAR >= " + t0);
} else {
query.append("TIMESTAMP >= " + t0 + " AND TIMESTAMP " + lastComp + " " + t1);
}
addFiltersToQuery(query);
String queryString = query.toString();
logger.debug(queryString);
int results = 0;
try {
Statement stm = traceDB.getConnection().createStatement();
ResultSet rs = stm.executeQuery(queryString);
while (rs.next()) {
if (monitor.isCanceled())
return results;
PendingDuration d = new PendingDuration(rs.getInt(1));
d.start = rs.getLong(2);
d.end = rs.getLong(3);
managePendingDuration(d, t0, t1, values, pending);
results++;
}
stm.close();
} catch (SQLException e) {
throw new SoCTraceException(e);
}
logger.debug("end");
logger.debug(pending.toString());
logger.debug(values.toString());
return results;
}
/**
* Manage the pending duration for the given time interval, as explained in
* {@link #managePendingDuration(PendingDuration, long, long, Map, List)}.
*
* @param t0
* start timestamp
* @param t1
* end timestamp
* @param values
* pie chart values
*/
private void managePendingDurations(long t0, long t1, Map<String, Double> values) {
List<PendingDuration> newPending = new ArrayList<>();
for (Iterator<PendingDuration> it = pending.iterator(); it.hasNext();) {
if (managePendingDuration(it.next(), t0, t1, values, newPending)) {
it.remove();
}
}
if (!newPending.isEmpty()) {
pending.addAll(newPending);
}
}
/**
* Process a pending duration in a given time interval.
*
* This means that the part of the passed pending duration intersecting the interval will be
* added to the pie chart values, while the remaining parts of the passed pending duration (if
* any) will be added to the list of new pending durations.
*
* @param d
* pending duration to process
* @param t0
* start timestamp
* @param t1
* end timestamp
* @param values
* pie chart values
* @param newPending
* list containing the new pending durations
* @return <true> if the passed pending duration intersects the passed interval and one or two
* new pending durations have been added to the list of new pending durations
*/
private boolean managePendingDuration(PendingDuration d, long t0, long t1,
Map<String, Double> values, List<PendingDuration> newPending) {
String etName = etMap.get(d.typeId);
boolean modified = false;
if (d.start < t0 && d.end > t0) {
// d intersects t0: cut the part before
PendingDuration before = new PendingDuration(d.typeId);
before.start = d.start;
before.end = t0;
newPending.add(before);
d.start = t0;
modified = true;
}
if (d.start < t1 && d.end > t1) {
// d intersects t1: cut the part after
PendingDuration after = new PendingDuration(d.typeId);
after.start = t1;
after.end = d.end;
newPending.add(after);
d.end = t1;
modified = true;
}
if (d.start >= t0 && d.end <= t1) {
// the remaining part is contained in [t0, t1]
Double remainingDuration = ((Long) (d.end - d.start)).doubleValue();
if (remainingDuration > 0) {
modified = true;
if (!values.containsKey(etName))
values.put(etName, remainingDuration);
else
values.put(etName, remainingDuration + values.get(etName));
}
}
return modified;
}
/**
* Lazily load the event type map for the duration category.
*
* @param traceDB
* trace DB object
* @throws SoCTraceException
*/
private void loadEventTypeMap(TraceDBObject traceDB) throws SoCTraceException {
// load all types (do it only once)
if (etMap == null) {
EventTypeQuery etq = new EventTypeQuery(traceDB);
etq.setElementWhere(new SimpleCondition("CATEGORY", ComparisonOperation.EQ, String
.valueOf(getDurationCategory())));
List<EventType> etl = etq.getList();
etMap = new HashMap<>();
for (EventType et : etl) {
etMap.put(et.getId(), et.getName());
}
}
}
}