1
/*******************************************************************************
2
* Copyright (c) 2012 Ericsson
3
* Copyright (c) 2010, 2011 Ćcole Polytechnique de MontrĆ©al
4
* Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
6
* All rights reserved. This program and the accompanying materials are
7
* made available under the terms of the Eclipse Public License v1.0 which
8
* accompanies this distribution, and is available at
9
* http://www.eclipse.org/legal/epl-v10.html
11
*******************************************************************************/
13
package org.eclipse.linuxtools.internal.tmf.core.statesystem.historytree;
16
import java.io.FileInputStream;
17
import java.io.IOException;
18
import java.io.PrintWriter;
19
import java.nio.ByteBuffer;
20
import java.nio.ByteOrder;
21
import java.nio.channels.FileChannel;
22
import java.util.Vector;
24
import org.eclipse.linuxtools.tmf.core.exceptions.TimeRangeException;
27
* Meta-container for the History Tree. This structure contains all the
28
* high-level data relevant to the tree.
35
private static final int HISTORY_FILE_MAGIC_NUMBER = 0x05FFA900;
38
* File format version. Increment minor on backwards-compatible changes.
39
* Increment major + set minor back to 0 when breaking compatibility.
41
private static final int MAJOR_VERSION = 3;
42
private static final byte MINOR_VERSION = 0;
45
* Tree-specific configuration
47
/* Container for all the configuration constants */
48
protected final HTConfig config;
50
/* Reader/writer object */
51
private final HT_IO treeIO;
54
* Variable Fields (will change throughout the existance of the SHT)
56
/* Latest timestamp found in the tree (at any given moment) */
59
/* How many nodes exist in this tree, total */
60
private int nodeCount;
62
/* "Cache" to keep the active nodes in memory */
63
protected Vector<CoreNode> latestBranch;
66
* Create a new State History from scratch, using a SHTConfig object for
72
private HistoryTree(HTConfig conf) throws IOException {
74
* Simple assertion to make sure we have enough place in the 0th block
75
* for the tree configuration
77
assert (conf.blockSize >= getTreeHeaderSize());
80
treeEnd = conf.treeStart;
82
latestBranch = new Vector<CoreNode>();
84
/* Prepare the IO object */
85
treeIO = new HT_IO(this, true);
87
/* Add the first node to the tree */
88
CoreNode firstNode = initNewCoreNode(-1, conf.treeStart);
89
latestBranch.add(firstNode);
93
* "New State History" constructor, which doesn't use SHTConfig but the
94
* individual values separately. Kept for now for backwards compatibility,
95
* but you should definitely consider using SHTConfig instead (since its
96
* contents can then change without directly affecting SHT's API).
98
HistoryTree(File newStateFile, int blockSize, int maxChildren,
99
long startTime) throws IOException {
100
this(new HTConfig(newStateFile, blockSize, maxChildren, startTime));
104
* "Reader" constructor : instantiate a SHTree from an existing tree file on
107
* @param existingFileName
108
* Path/filename of the history-file we are to open
109
* @throws IOException
111
HistoryTree(File existingStateFile) throws IOException {
113
* Open the file ourselves, get the tree header information we need,
114
* then pass on the descriptor to the TreeIO object.
116
int rootNodeSeqNb, res;
120
/* Java I/O mumbo jumbo... */
121
if (!existingStateFile.exists()) {
122
throw new IOException("Selected state file does not exist"); //$NON-NLS-1$
124
if (existingStateFile.length() <= 0) {
125
throw new IOException("Invalid state file selected, " + //$NON-NLS-1$
126
"target file is empty"); //$NON-NLS-1$
129
FileInputStream fis = new FileInputStream(existingStateFile);
130
ByteBuffer buffer = ByteBuffer.allocate(getTreeHeaderSize());
131
FileChannel fc = fis.getChannel();
132
buffer.order(ByteOrder.LITTLE_ENDIAN);
138
* Check the magic number,to make sure we're opening the right type of
141
res = buffer.getInt();
142
if (res != HISTORY_FILE_MAGIC_NUMBER) {
145
throw new IOException("Selected file does not" + //$NON-NLS-1$
146
"look like a History Tree file"); //$NON-NLS-1$
149
res = buffer.getInt(); /* Major version number */
150
if (res != MAJOR_VERSION) {
153
throw new IOException("Select History Tree file is of an older " //$NON-NLS-1$
154
+ "format. Please use a previous version of " //$NON-NLS-1$
155
+ "the parser to open it."); //$NON-NLS-1$
158
res = buffer.getInt(); /* Minor version number */
160
bs = buffer.getInt(); /* Block Size */
161
maxc = buffer.getInt(); /* Max nb of children per node */
163
this.nodeCount = buffer.getInt();
164
rootNodeSeqNb = buffer.getInt();
165
startTime = buffer.getLong();
167
this.config = new HTConfig(existingStateFile, bs, maxc, startTime);
171
* FIXME We close fis here and the TreeIO will then reopen the same
172
* file, not extremely elegant. But how to pass the information here to
175
this.treeIO = new HT_IO(this, false);
177
rebuildLatestBranch(rootNodeSeqNb);
178
this.treeEnd = latestBranch.firstElement().getNodeEnd();
181
* Make sure the history start time we read previously is consistent
182
* with was is actually in the root node.
184
if (startTime != latestBranch.firstElement().getNodeStart()) {
187
throw new IOException("Inconsistent start times in the" + //$NON-NLS-1$
188
"history file, it might be corrupted."); //$NON-NLS-1$
193
* "Save" the tree to disk. This method will cause the treeIO object to
194
* commit all nodes to disk and then return the RandomAccessFile descriptor
195
* so the Tree object can save its configuration into the header of the
198
* @param requestedEndTime
200
void closeTree(long requestedEndTime) {
206
* Work-around the "empty branches" that get created when the root node
207
* becomes full. Overwrite the tree's end time with the original wanted
208
* end-time, to ensure no queries are sent into those empty nodes.
210
* This won't be needed once extended nodes are implemented.
212
this.treeEnd = requestedEndTime;
214
/* Close off the latest branch of the tree */
215
for (i = 0; i < latestBranch.size(); i++) {
216
latestBranch.get(i).closeThisNode(treeEnd);
217
treeIO.writeNode(latestBranch.get(i));
220
/* Only use this for debugging purposes, it's VERY slow! */
221
// this.checkIntegrity();
223
fc = treeIO.getFcOut();
224
buffer = ByteBuffer.allocate(getTreeHeaderSize());
225
buffer.order(ByteOrder.LITTLE_ENDIAN);
228
/* Save the config of the tree to the header of the file */
232
buffer.putInt(HISTORY_FILE_MAGIC_NUMBER);
234
buffer.putInt(MAJOR_VERSION);
235
buffer.putInt(MINOR_VERSION);
237
buffer.putInt(config.blockSize);
238
buffer.putInt(config.maxChildren);
240
buffer.putInt(nodeCount);
242
/* root node seq. nb */
243
buffer.putInt(latestBranch.firstElement().getSequenceNumber());
245
/* start time of this history */
246
buffer.putLong(latestBranch.firstElement().getNodeStart());
249
res = fc.write(buffer);
250
assert (res <= getTreeHeaderSize());
251
/* done writing the file header */
253
} catch (IOException e) {
254
/* We should not have any problems at this point... */
259
} catch (IOException e) {
270
long getTreeStart() {
271
return config.treeStart;
287
* Rebuild the latestBranch "cache" object by reading the nodes from disk
288
* (When we are opening an existing file on disk and want to append to it,
291
* @param rootNodeSeqNb
292
* The sequence number of the root node, so we know where to
295
private void rebuildLatestBranch(int rootNodeSeqNb) {
296
HTNode nextChildNode;
298
this.latestBranch = new Vector<CoreNode>();
300
nextChildNode = treeIO.readNodeFromDisk(rootNodeSeqNb);
301
latestBranch.add((CoreNode) nextChildNode);
302
while (latestBranch.lastElement().getNbChildren() > 0) {
303
nextChildNode = treeIO.readNodeFromDisk(latestBranch.lastElement().getLatestChild());
304
latestBranch.add((CoreNode) nextChildNode);
309
* Insert an interval in the tree
313
void insertInterval(HTInterval interval) throws TimeRangeException {
314
if (interval.getStartTime() < config.treeStart) {
315
throw new TimeRangeException();
317
tryInsertAtNode(interval, latestBranch.size() - 1);
321
* Inner method to find in which node we should add the interval.
324
* The interval to add to the tree
326
* The index *in the latestBranch* where we are trying the
329
private void tryInsertAtNode(HTInterval interval, int indexOfNode) {
330
HTNode targetNode = latestBranch.get(indexOfNode);
332
/* Verify if there is enough room in this node to store this interval */
333
if (interval.getIntervalSize() > targetNode.getNodeFreeSpace()) {
334
/* Nope, not enough room. Insert in a new sibling instead. */
335
addSiblingNode(indexOfNode);
336
tryInsertAtNode(interval, latestBranch.size() - 1);
340
/* Make sure the interval time range fits this node */
341
if (interval.getStartTime() < targetNode.getNodeStart()) {
343
* No, this interval starts before the startTime of this node. We
344
* need to check recursively in parents if it can fit.
346
assert (indexOfNode >= 1);
347
tryInsertAtNode(interval, indexOfNode - 1);
352
* Ok, there is room, and the interval fits in this time slot. Let's add
355
targetNode.addInterval(interval);
357
/* Update treeEnd if needed */
358
if (interval.getEndTime() > this.treeEnd) {
359
this.treeEnd = interval.getEndTime();
365
* Method to add a sibling to any node in the latest branch. This will add
366
* children back down to the leaf level, if needed.
369
* The index in latestBranch where we start adding
371
private void addSiblingNode(int indexOfNode) {
373
CoreNode newNode, prevNode;
374
long splitTime = treeEnd;
376
assert (indexOfNode < latestBranch.size());
378
/* Check if we need to add a new root node */
379
if (indexOfNode == 0) {
384
/* Check if we can indeed add a child to the target parent */
385
if (latestBranch.get(indexOfNode - 1).getNbChildren() == config.maxChildren) {
386
/* If not, add a branch starting one level higher instead */
387
addSiblingNode(indexOfNode - 1);
391
/* Split off the new branch from the old one */
392
for (i = indexOfNode; i < latestBranch.size(); i++) {
393
latestBranch.get(i).closeThisNode(splitTime);
394
treeIO.writeNode(latestBranch.get(i));
396
prevNode = latestBranch.get(i - 1);
397
newNode = initNewCoreNode(prevNode.getSequenceNumber(),
399
prevNode.linkNewChild(newNode);
401
latestBranch.set(i, newNode);
407
* Similar to the previous method, except here we rebuild a completely new
410
private void addNewRootNode() {
412
CoreNode oldRootNode, newRootNode, newNode, prevNode;
413
long splitTime = this.treeEnd;
415
oldRootNode = latestBranch.firstElement();
416
newRootNode = initNewCoreNode(-1, config.treeStart);
418
/* Tell the old root node that it isn't root anymore */
419
oldRootNode.setParentSequenceNumber(newRootNode.getSequenceNumber());
421
/* Close off the whole current latestBranch */
422
for (i = 0; i < latestBranch.size(); i++) {
423
latestBranch.get(i).closeThisNode(splitTime);
424
treeIO.writeNode(latestBranch.get(i));
427
/* Link the new root to its first child (the previous root node) */
428
newRootNode.linkNewChild(oldRootNode);
430
/* Rebuild a new latestBranch */
431
depth = latestBranch.size();
432
latestBranch = new Vector<CoreNode>();
433
latestBranch.add(newRootNode);
434
for (i = 1; i < depth + 1; i++) {
435
prevNode = latestBranch.get(i - 1);
436
newNode = initNewCoreNode(prevNode.getParentSequenceNumber(),
438
prevNode.linkNewChild(newNode);
439
latestBranch.add(newNode);
444
* Add a new empty node to the tree.
446
* @param parentSeqNumber
447
* Sequence number of this node's parent
449
* Start time of the new node
450
* @return The newly created node
452
private CoreNode initNewCoreNode(int parentSeqNumber, long startTime) {
453
CoreNode newNode = new CoreNode(this, this.nodeCount, parentSeqNumber,
457
/* Update the treeEnd if needed */
458
if (startTime >= this.treeEnd) {
459
this.treeEnd = startTime + 1;
465
* Inner method to select the next child of the current node intersecting
466
* the given timestamp. Useful for moving down the tree following one
471
* @return The child node intersecting t
473
HTNode selectNextChild(CoreNode currentNode, long t) {
474
assert (currentNode.getNbChildren() > 0);
475
int potentialNextSeqNb = currentNode.getSequenceNumber();
477
for (int i = 0; i < currentNode.getNbChildren(); i++) {
478
if (t >= currentNode.getChildStart(i)) {
479
potentialNextSeqNb = currentNode.getChild(i);
485
* Once we exit this loop, we should have found a children to follow. If
486
* we didn't, there's a problem.
488
assert (potentialNextSeqNb != currentNode.getSequenceNumber());
491
* Since this code path is quite performance-critical, avoid iterating
492
* through the whole latestBranch array if we know for sure the next
493
* node has to be on disk
495
if (currentNode.isDone()) {
496
return treeIO.readNodeFromDisk(potentialNextSeqNb);
498
return treeIO.readNode(potentialNextSeqNb);
502
* Helper function to get the size of the "tree header" in the tree-file The
503
* nodes will use this offset to know where they should be in the file. This
504
* should always be a multiple of 4K.
506
static int getTreeHeaderSize() {
511
return config.stateFile.length();
515
* @name Test/debugging functions
518
/* Only used for debugging, shouldn't be externalized */
519
@SuppressWarnings("nls")
520
boolean checkNodeIntegrity(HTNode zenode) {
524
StringBuffer buf = new StringBuffer();
527
// FIXME /* Only testing Core Nodes for now */
528
if (!(zenode instanceof CoreNode)) {
532
node = (CoreNode) zenode;
535
* Test that this node's start and end times match the start of the
536
* first child and the end of the last child, respectively
538
if (node.getNbChildren() > 0) {
539
otherNode = treeIO.readNode(node.getChild(0));
540
if (node.getNodeStart() != otherNode.getNodeStart()) {
541
buf.append("Start time of node (" + node.getNodeStart() + ") "
542
+ "does not match start time of first child " + "("
543
+ otherNode.getNodeStart() + "), " + "node #"
544
+ otherNode.getSequenceNumber() + ")\n");
548
otherNode = treeIO.readNode(node.getLatestChild());
549
if (node.getNodeEnd() != otherNode.getNodeEnd()) {
550
buf.append("End time of node (" + node.getNodeEnd()
551
+ ") does not match end time of last child ("
552
+ otherNode.getNodeEnd() + ", node #"
553
+ otherNode.getSequenceNumber() + ")\n");
560
* Test that the childStartTimes[] array matches the real nodes' start
563
for (int i = 0; i < node.getNbChildren(); i++) {
564
otherNode = treeIO.readNode(node.getChild(i));
565
if (otherNode.getNodeStart() != node.getChildStart(i)) {
566
buf.append(" Expected start time of child node #"
567
+ node.getChild(i) + ": " + node.getChildStart(i)
568
+ "\n" + " Actual start time of node #"
569
+ otherNode.getSequenceNumber() + ": "
570
+ otherNode.getNodeStart() + "\n");
576
System.out.println("");
577
System.out.println("SHT: Integrity check failed for node #"
578
+ node.getSequenceNumber() + ":");
579
System.out.println(buf.toString());
584
void checkIntegrity() {
585
for (int i = 0; i < nodeCount; i++) {
586
checkNodeIntegrity(treeIO.readNode(i));
590
/* Only used for debugging, shouldn't be externalized */
591
@SuppressWarnings("nls")
593
public String toString() {
594
return "Information on the current tree:\n\n" + "Blocksize: "
595
+ config.blockSize + "\n" + "Max nb. of children per node: "
596
+ config.maxChildren + "\n" + "Number of nodes: " + nodeCount
597
+ "\n" + "Depth of the tree: " + latestBranch.size() + "\n"
598
+ "Size of the treefile: " + this.getFileSize() + "\n"
599
+ "Root node has sequence number: "
600
+ latestBranch.firstElement().getSequenceNumber() + "\n"
601
+ "'Latest leaf' has sequence number: "
602
+ latestBranch.lastElement().getSequenceNumber();
605
private int curDepth;
608
* Start at currentNode and print the contents of all its children, in
609
* pre-order. Give the root node in parameter to visit the whole tree, and
610
* have a nice overview.
612
@SuppressWarnings("nls")
613
private void preOrderPrint(PrintWriter writer, boolean printIntervals,
614
CoreNode currentNode) {
615
/* Only used for debugging, shouldn't be externalized */
619
writer.println(currentNode.toString());
620
if (printIntervals) {
621
currentNode.debugPrintIntervals(writer);
625
for (i = 0; i < currentNode.getNbChildren(); i++) {
626
nextNode = treeIO.readNode(currentNode.getChild(i));
627
assert (nextNode instanceof CoreNode); // TODO temporary
628
for (j = 0; j < curDepth - 1; j++) {
632
preOrderPrint(writer, printIntervals, (CoreNode) nextNode);
639
* Print out the full tree for debugging purposes
642
* PrintWriter in which to write the output
643
* @param printIntervals
644
* Says if you want to output the full interval information
646
void debugPrintFullTree(PrintWriter writer, boolean printIntervals) {
647
/* Only used for debugging, shouldn't be externalized */
649
this.preOrderPrint(writer, false, latestBranch.firstElement());
651
if (printIntervals) {
652
writer.println("\nDetails of intervals:"); //$NON-NLS-1$
654
this.preOrderPrint(writer, true, latestBranch.firstElement());
656
writer.println('\n');