/*******************************************************************************
* Copyright (c) 2012, 2014 Ericsson
* Copyright (c) 2010, 2011 École Polytechnique de Montréal
* Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
*
* 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
*
*******************************************************************************/
package fr.inria.linuxtools.statesystem.core.backend.historytree;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.channels.ClosedChannelException;
import java.util.List;
import fr.inria.linuxtools.internal.statesystem.core.backend.historytree.CoreNode;
import fr.inria.linuxtools.internal.statesystem.core.backend.historytree.HTConfig;
import fr.inria.linuxtools.internal.statesystem.core.backend.historytree.HTInterval;
import fr.inria.linuxtools.internal.statesystem.core.backend.historytree.HTNode;
import fr.inria.linuxtools.internal.statesystem.core.backend.historytree.HistoryTree;
import fr.inria.linuxtools.statesystem.core.backend.IStateHistoryBackend;
import fr.inria.linuxtools.statesystem.core.exceptions.StateSystemDisposedException;
import fr.inria.linuxtools.statesystem.core.exceptions.TimeRangeException;
import fr.inria.linuxtools.statesystem.core.interval.ITmfStateInterval;
import fr.inria.linuxtools.statesystem.core.statevalue.ITmfStateValue;
import fr.inria.linuxtools.statesystem.core.statevalue.TmfStateValue;
/**
* History Tree backend for storing a state history. This is the basic version
* that runs in the same thread as the class creating it.
*
* @author Alexandre Montplaisir
* @since 3.0
*/
public class HistoryTreeBackend implements IStateHistoryBackend {
/**
* The history tree that sits underneath.
*
* Using default visibility to only allow {@link ThreadedHistoryTreeBackend}
* to see it.
*/
final HistoryTree sht;
/** Indicates if the history tree construction is done */
protected boolean isFinishedBuilding = false;
/**
* Constructor for new history files. Use this when creating a new history
* from scratch.
*
* @param newStateFile
* The filename/location where to store the state history (Should
* end in .ht)
* @param blockSize
* The size of the blocks in the history file. This should be a
* multiple of 4096.
* @param maxChildren
* The maximum number of children each core node can have
* @param providerVersion
* Version of of the state provider. We will only try to reopen
* existing files if this version matches the one in the
* framework.
* @param startTime
* The earliest time stamp that will be stored in the history
* @throws IOException
* Thrown if we can't create the file for some reason
*/
public HistoryTreeBackend(File newStateFile, int blockSize,
int maxChildren, int providerVersion, long startTime) throws IOException {
final HTConfig conf = new HTConfig(newStateFile, blockSize, maxChildren,
providerVersion, startTime);
sht = new HistoryTree(conf);
}
/**
* Constructor for new history files. Use this when creating a new history
* from scratch. This version supplies sane defaults for the configuration
* parameters.
*
* @param newStateFile
* The filename/location where to store the state history (Should
* end in .ht)
* @param providerVersion
* Version of of the state provider. We will only try to reopen
* existing files if this version matches the one in the
* framework.
* @param startTime
* The earliest time stamp that will be stored in the history
* @throws IOException
* Thrown if we can't create the file for some reason
*/
public HistoryTreeBackend(File newStateFile, int providerVersion, long startTime)
throws IOException {
this(newStateFile, 64 * 1024, 50, providerVersion, startTime);
}
/**
* Existing history constructor. Use this to open an existing state-file.
*
* @param existingStateFile
* Filename/location of the history we want to load
* @param providerVersion
* Expected version of of the state provider plugin.
* @throws IOException
* If we can't read the file, if it doesn't exist, is not
* recognized, or if the version of the file does not match the
* expected providerVersion.
*/
public HistoryTreeBackend(File existingStateFile, int providerVersion)
throws IOException {
sht = new HistoryTree(existingStateFile, providerVersion);
isFinishedBuilding = true;
}
@Override
public long getStartTime() {
return sht.getTreeStart();
}
@Override
public long getEndTime() {
return sht.getTreeEnd();
}
@Override
public void insertPastState(long stateStartTime, long stateEndTime,
int quark, ITmfStateValue value) throws TimeRangeException {
HTInterval interval = new HTInterval(stateStartTime, stateEndTime,
quark, (TmfStateValue) value);
/* Start insertions at the "latest leaf" */
sht.insertInterval(interval);
}
@Override
public void finishedBuilding(long endTime) {
sht.closeTree(endTime);
isFinishedBuilding = true;
}
@Override
public FileInputStream supplyAttributeTreeReader() {
return sht.supplyATReader();
}
@Override
public File supplyAttributeTreeWriterFile() {
return sht.supplyATWriterFile();
}
@Override
public long supplyAttributeTreeWriterFilePosition() {
return sht.supplyATWriterFilePos();
}
@Override
public void removeFiles() {
sht.deleteFile();
}
@Override
public void dispose() {
if (isFinishedBuilding) {
sht.closeFile();
} else {
/*
* The build is being interrupted, delete the file we partially
* built since it won't be complete, so shouldn't be re-used in the
* future (.deleteFile() will close the file first)
*/
sht.deleteFile();
}
}
@Override
public void doQuery(List<ITmfStateInterval> stateInfo, long t)
throws TimeRangeException, StateSystemDisposedException {
if (!checkValidTime(t)) {
/* We can't possibly have information about this query */
throw new TimeRangeException();
}
/* We start by reading the information in the root node */
HTNode currentNode = sht.getRootNode();
currentNode.writeInfoFromNode(stateInfo, t);
/* Then we follow the branch down in the relevant children */
try {
while (currentNode.getNodeType() == HTNode.NodeType.CORE) {
currentNode = sht.selectNextChild((CoreNode) currentNode, t);
currentNode.writeInfoFromNode(stateInfo, t);
}
} catch (ClosedChannelException e) {
throw new StateSystemDisposedException(e);
}
/*
* The stateInfo should now be filled with everything needed, we pass
* the control back to the State System.
*/
return;
}
@Override
public ITmfStateInterval doSingularQuery(long t, int attributeQuark)
throws TimeRangeException, StateSystemDisposedException {
return getRelevantInterval(t, attributeQuark);
}
@Override
public boolean checkValidTime(long t) {
return (t >= sht.getTreeStart() && t <= sht.getTreeEnd());
}
/**
* Inner method to find the interval in the tree containing the requested
* key/timestamp pair, wherever in which node it is.
*
* @param t
* @param key
* @return The node containing the information we want
*/
private HTInterval getRelevantInterval(long t, int key)
throws TimeRangeException, StateSystemDisposedException {
if (!checkValidTime(t)) {
throw new TimeRangeException();
}
HTNode currentNode = sht.getRootNode();
HTInterval interval = currentNode.getRelevantInterval(key, t);
try {
while (interval == null && currentNode.getNodeType() == HTNode.NodeType.CORE) {
currentNode = sht.selectNextChild((CoreNode)currentNode, t);
interval = currentNode.getRelevantInterval(key, t);
}
} catch (ClosedChannelException e) {
throw new StateSystemDisposedException(e);
}
/*
* Since we should now have intervals at every attribute/timestamp
* combination, it should NOT be null here.
*/
assert (interval != null);
return interval;
}
/**
* Return the size of the tree history file
*
* @return The current size of the history file in bytes
*/
public long getFileSize() {
return sht.getFileSize();
}
/**
* Return the average node usage as a percentage (between 0 and 100)
*
* @return Average node usage %
*/
public int getAverageNodeUsage() {
HTNode node;
long total = 0;
long ret;
try {
for (int seq = 0; seq < sht.getNodeCount(); seq++) {
node = sht.readNode(seq);
total += node.getNodeUsagePercent();
}
} catch (ClosedChannelException e) {
e.printStackTrace();
}
ret = total / sht.getNodeCount();
assert (ret >= 0 && ret <= 100);
return (int) ret;
}
@Override
public void debugPrint(PrintWriter writer) {
/* By default don't print out all the intervals */
this.debugPrint(writer, false);
}
/**
* The basic debugPrint method will print the tree structure, but not their
* contents.
*
* This method here print the contents (the intervals) as well.
*
* @param writer
* The PrintWriter to which the debug info will be written
* @param printIntervals
* Should we also print every contained interval individually?
*/
public void debugPrint(PrintWriter writer, boolean printIntervals) {
/* Only used for debugging, shouldn't be externalized */
writer.println("------------------------------"); //$NON-NLS-1$
writer.println("State History Tree:\n"); //$NON-NLS-1$
writer.println(sht.toString());
writer.println("Average node utilization: " //$NON-NLS-1$
+ this.getAverageNodeUsage());
writer.println(""); //$NON-NLS-1$
sht.debugPrintFullTree(writer, printIntervals);
}
}