tmf: Move timestamps to their own package
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.core / src / org / eclipse / linuxtools / internal / tmf / core / statesystem / backends / historytree / HistoryTree.java
CommitLineData
a52fde77
AM
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>
3b7f5abe 5 *
a52fde77
AM
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
3b7f5abe 10 *
a52fde77
AM
11 *******************************************************************************/
12
f9a76cac 13package org.eclipse.linuxtools.internal.tmf.core.statesystem.backends.historytree;
a52fde77
AM
14
15import java.io.File;
16import java.io.FileInputStream;
17import java.io.IOException;
18import java.io.PrintWriter;
19import java.nio.ByteBuffer;
20import java.nio.ByteOrder;
3b7f5abe 21import java.nio.channels.ClosedChannelException;
a52fde77
AM
22import java.nio.channels.FileChannel;
23import java.util.Vector;
24
6d08acca 25import org.eclipse.linuxtools.tmf.core.exceptions.TimeRangeException;
a52fde77
AM
26
27/**
28 * Meta-container for the History Tree. This structure contains all the
29 * high-level data relevant to the tree.
3b7f5abe 30 *
a52fde77 31 * @author alexmont
3b7f5abe 32 *
a52fde77
AM
33 */
34class HistoryTree {
35
36 private static final int HISTORY_FILE_MAGIC_NUMBER = 0x05FFA900;
37
38 /**
39 * File format version. Increment minor on backwards-compatible changes.
40 * Increment major + set minor back to 0 when breaking compatibility.
41 */
42 private static final int MAJOR_VERSION = 3;
43 private static final byte MINOR_VERSION = 0;
44
45 /**
46 * Tree-specific configuration
47 */
48 /* Container for all the configuration constants */
49 protected final HTConfig config;
50
51 /* Reader/writer object */
52 private final HT_IO treeIO;
53
54 /**
55 * Variable Fields (will change throughout the existance of the SHT)
56 */
57 /* Latest timestamp found in the tree (at any given moment) */
58 private long treeEnd;
59
60 /* How many nodes exist in this tree, total */
61 private int nodeCount;
62
63 /* "Cache" to keep the active nodes in memory */
64 protected Vector<CoreNode> latestBranch;
65
66 /**
67 * Create a new State History from scratch, using a SHTConfig object for
68 * configuration
3b7f5abe 69 *
a52fde77
AM
70 * @param conf
71 * @throws IOException
72 */
73 private HistoryTree(HTConfig conf) throws IOException {
74 /*
75 * Simple assertion to make sure we have enough place in the 0th block
76 * for the tree configuration
77 */
78 assert (conf.blockSize >= getTreeHeaderSize());
79
80 config = conf;
81 treeEnd = conf.treeStart;
82 nodeCount = 0;
83 latestBranch = new Vector<CoreNode>();
84
85 /* Prepare the IO object */
86 treeIO = new HT_IO(this, true);
87
88 /* Add the first node to the tree */
89 CoreNode firstNode = initNewCoreNode(-1, conf.treeStart);
90 latestBranch.add(firstNode);
91 }
92
93 /**
94 * "New State History" constructor, which doesn't use SHTConfig but the
95 * individual values separately. Kept for now for backwards compatibility,
96 * but you should definitely consider using SHTConfig instead (since its
97 * contents can then change without directly affecting SHT's API).
98 */
99 HistoryTree(File newStateFile, int blockSize, int maxChildren,
100 long startTime) throws IOException {
101 this(new HTConfig(newStateFile, blockSize, maxChildren, startTime));
102 }
103
104 /**
105 * "Reader" constructor : instantiate a SHTree from an existing tree file on
106 * disk
3b7f5abe 107 *
a52fde77
AM
108 * @param existingFileName
109 * Path/filename of the history-file we are to open
110 * @throws IOException
111 */
112 HistoryTree(File existingStateFile) throws IOException {
113 /*
114 * Open the file ourselves, get the tree header information we need,
115 * then pass on the descriptor to the TreeIO object.
116 */
117 int rootNodeSeqNb, res;
118 int bs, maxc;
fb12b0c2 119 long startTime;
a52fde77
AM
120
121 /* Java I/O mumbo jumbo... */
fee997a5
AM
122 if (!existingStateFile.exists()) {
123 throw new IOException("Selected state file does not exist"); //$NON-NLS-1$
124 }
fb12b0c2
AM
125 if (existingStateFile.length() <= 0) {
126 throw new IOException("Invalid state file selected, " + //$NON-NLS-1$
127 "target file is empty"); //$NON-NLS-1$
a52fde77
AM
128 }
129
130 FileInputStream fis = new FileInputStream(existingStateFile);
131 ByteBuffer buffer = ByteBuffer.allocate(getTreeHeaderSize());
132 FileChannel fc = fis.getChannel();
133 buffer.order(ByteOrder.LITTLE_ENDIAN);
134 buffer.clear();
135 fc.read(buffer);
136 buffer.flip();
137
138 /*
139 * Check the magic number,to make sure we're opening the right type of
140 * file
141 */
142 res = buffer.getInt();
143 if (res != HISTORY_FILE_MAGIC_NUMBER) {
6f04204e
AM
144 fc.close();
145 fis.close();
fb12b0c2
AM
146 throw new IOException("Selected file does not" + //$NON-NLS-1$
147 "look like a History Tree file"); //$NON-NLS-1$
a52fde77
AM
148 }
149
150 res = buffer.getInt(); /* Major version number */
151 if (res != MAJOR_VERSION) {
6f04204e
AM
152 fc.close();
153 fis.close();
a52fde77
AM
154 throw new IOException("Select History Tree file is of an older " //$NON-NLS-1$
155 + "format. Please use a previous version of " //$NON-NLS-1$
156 + "the parser to open it."); //$NON-NLS-1$
157 }
158
159 res = buffer.getInt(); /* Minor version number */
160
161 bs = buffer.getInt(); /* Block Size */
162 maxc = buffer.getInt(); /* Max nb of children per node */
163
164 this.nodeCount = buffer.getInt();
165 rootNodeSeqNb = buffer.getInt();
fb12b0c2 166 startTime = buffer.getLong();
a52fde77 167
fb12b0c2 168 this.config = new HTConfig(existingStateFile, bs, maxc, startTime);
6f04204e 169 fc.close();
a52fde77
AM
170 fis.close();
171 /*
172 * FIXME We close fis here and the TreeIO will then reopen the same
173 * file, not extremely elegant. But how to pass the information here to
174 * the SHT otherwise?
175 */
176 this.treeIO = new HT_IO(this, false);
177
178 rebuildLatestBranch(rootNodeSeqNb);
a52fde77 179 this.treeEnd = latestBranch.firstElement().getNodeEnd();
fb12b0c2
AM
180
181 /*
182 * Make sure the history start time we read previously is consistent
183 * with was is actually in the root node.
184 */
185 if (startTime != latestBranch.firstElement().getNodeStart()) {
186 fc.close();
187 fis.close();
188 throw new IOException("Inconsistent start times in the" + //$NON-NLS-1$
189 "history file, it might be corrupted."); //$NON-NLS-1$
190 }
a52fde77
AM
191 }
192
193 /**
194 * "Save" the tree to disk. This method will cause the treeIO object to
195 * commit all nodes to disk and then return the RandomAccessFile descriptor
196 * so the Tree object can save its configuration into the header of the
197 * file.
3b7f5abe 198 *
a52fde77
AM
199 * @param requestedEndTime
200 */
6a1074ce 201 void closeTree(long requestedEndTime) {
a52fde77
AM
202 FileChannel fc;
203 ByteBuffer buffer;
204 int i, res;
205
3b7f5abe 206 /*
6a1074ce
AM
207 * Work-around the "empty branches" that get created when the root node
208 * becomes full. Overwrite the tree's end time with the original wanted
209 * end-time, to ensure no queries are sent into those empty nodes.
3b7f5abe 210 *
6a1074ce
AM
211 * This won't be needed once extended nodes are implemented.
212 */
213 this.treeEnd = requestedEndTime;
214
a52fde77
AM
215 /* Close off the latest branch of the tree */
216 for (i = 0; i < latestBranch.size(); i++) {
217 latestBranch.get(i).closeThisNode(treeEnd);
218 treeIO.writeNode(latestBranch.get(i));
219 }
220
221 /* Only use this for debugging purposes, it's VERY slow! */
222 // this.checkIntegrity();
223
224 fc = treeIO.getFcOut();
225 buffer = ByteBuffer.allocate(getTreeHeaderSize());
226 buffer.order(ByteOrder.LITTLE_ENDIAN);
227 buffer.clear();
228
229 /* Save the config of the tree to the header of the file */
230 try {
231 fc.position(0);
232
233 buffer.putInt(HISTORY_FILE_MAGIC_NUMBER);
234
235 buffer.putInt(MAJOR_VERSION);
236 buffer.putInt(MINOR_VERSION);
237
238 buffer.putInt(config.blockSize);
239 buffer.putInt(config.maxChildren);
240
241 buffer.putInt(nodeCount);
242
243 /* root node seq. nb */
244 buffer.putInt(latestBranch.firstElement().getSequenceNumber());
245
fb12b0c2
AM
246 /* start time of this history */
247 buffer.putLong(latestBranch.firstElement().getNodeStart());
248
a52fde77
AM
249 buffer.flip();
250 res = fc.write(buffer);
251 assert (res <= getTreeHeaderSize());
252 /* done writing the file header */
253
254 } catch (IOException e) {
6f04204e 255 /* We should not have any problems at this point... */
a52fde77 256 e.printStackTrace();
6f04204e
AM
257 } finally {
258 try {
259 fc.close();
260 } catch (IOException e) {
261 e.printStackTrace();
262 }
a52fde77
AM
263 }
264 return;
265 }
266
267 /**
268 * @name Accessors
269 */
ab604305 270
a52fde77
AM
271 long getTreeStart() {
272 return config.treeStart;
273 }
274
275 long getTreeEnd() {
276 return treeEnd;
277 }
278
279 int getNodeCount() {
280 return nodeCount;
281 }
282
283 HT_IO getTreeIO() {
284 return treeIO;
285 }
286
287 /**
288 * Rebuild the latestBranch "cache" object by reading the nodes from disk
289 * (When we are opening an existing file on disk and want to append to it,
290 * for example).
3b7f5abe 291 *
a52fde77
AM
292 * @param rootNodeSeqNb
293 * The sequence number of the root node, so we know where to
294 * start
3b7f5abe 295 * @throws ClosedChannelException
a52fde77 296 */
3b7f5abe 297 private void rebuildLatestBranch(int rootNodeSeqNb) throws ClosedChannelException {
a52fde77
AM
298 HTNode nextChildNode;
299
300 this.latestBranch = new Vector<CoreNode>();
301
302 nextChildNode = treeIO.readNodeFromDisk(rootNodeSeqNb);
303 latestBranch.add((CoreNode) nextChildNode);
304 while (latestBranch.lastElement().getNbChildren() > 0) {
305 nextChildNode = treeIO.readNodeFromDisk(latestBranch.lastElement().getLatestChild());
306 latestBranch.add((CoreNode) nextChildNode);
307 }
308 }
309
310 /**
311 * Insert an interval in the tree
3b7f5abe 312 *
a52fde77
AM
313 * @param interval
314 */
315 void insertInterval(HTInterval interval) throws TimeRangeException {
316 if (interval.getStartTime() < config.treeStart) {
317 throw new TimeRangeException();
318 }
319 tryInsertAtNode(interval, latestBranch.size() - 1);
320 }
321
322 /**
323 * Inner method to find in which node we should add the interval.
3b7f5abe 324 *
a52fde77
AM
325 * @param interval
326 * The interval to add to the tree
327 * @param indexOfNode
328 * The index *in the latestBranch* where we are trying the
329 * insertion
330 */
331 private void tryInsertAtNode(HTInterval interval, int indexOfNode) {
332 HTNode targetNode = latestBranch.get(indexOfNode);
333
334 /* Verify if there is enough room in this node to store this interval */
335 if (interval.getIntervalSize() > targetNode.getNodeFreeSpace()) {
336 /* Nope, not enough room. Insert in a new sibling instead. */
337 addSiblingNode(indexOfNode);
338 tryInsertAtNode(interval, latestBranch.size() - 1);
339 return;
340 }
341
342 /* Make sure the interval time range fits this node */
343 if (interval.getStartTime() < targetNode.getNodeStart()) {
344 /*
345 * No, this interval starts before the startTime of this node. We
346 * need to check recursively in parents if it can fit.
347 */
348 assert (indexOfNode >= 1);
349 tryInsertAtNode(interval, indexOfNode - 1);
350 return;
351 }
352
353 /*
354 * Ok, there is room, and the interval fits in this time slot. Let's add
355 * it.
356 */
357 targetNode.addInterval(interval);
358
359 /* Update treeEnd if needed */
360 if (interval.getEndTime() > this.treeEnd) {
361 this.treeEnd = interval.getEndTime();
362 }
363 return;
364 }
365
366 /**
367 * Method to add a sibling to any node in the latest branch. This will add
368 * children back down to the leaf level, if needed.
3b7f5abe 369 *
a52fde77
AM
370 * @param indexOfNode
371 * The index in latestBranch where we start adding
372 */
373 private void addSiblingNode(int indexOfNode) {
374 int i;
375 CoreNode newNode, prevNode;
376 long splitTime = treeEnd;
377
378 assert (indexOfNode < latestBranch.size());
379
380 /* Check if we need to add a new root node */
381 if (indexOfNode == 0) {
382 addNewRootNode();
383 return;
384 }
385
386 /* Check if we can indeed add a child to the target parent */
387 if (latestBranch.get(indexOfNode - 1).getNbChildren() == config.maxChildren) {
388 /* If not, add a branch starting one level higher instead */
389 addSiblingNode(indexOfNode - 1);
390 return;
391 }
392
393 /* Split off the new branch from the old one */
394 for (i = indexOfNode; i < latestBranch.size(); i++) {
395 latestBranch.get(i).closeThisNode(splitTime);
396 treeIO.writeNode(latestBranch.get(i));
397
398 prevNode = latestBranch.get(i - 1);
399 newNode = initNewCoreNode(prevNode.getSequenceNumber(),
400 splitTime + 1);
401 prevNode.linkNewChild(newNode);
402
403 latestBranch.set(i, newNode);
404 }
405 return;
406 }
407
408 /**
409 * Similar to the previous method, except here we rebuild a completely new
410 * latestBranch
411 */
412 private void addNewRootNode() {
413 int i, depth;
414 CoreNode oldRootNode, newRootNode, newNode, prevNode;
415 long splitTime = this.treeEnd;
416
417 oldRootNode = latestBranch.firstElement();
418 newRootNode = initNewCoreNode(-1, config.treeStart);
419
420 /* Tell the old root node that it isn't root anymore */
421 oldRootNode.setParentSequenceNumber(newRootNode.getSequenceNumber());
422
423 /* Close off the whole current latestBranch */
424 for (i = 0; i < latestBranch.size(); i++) {
425 latestBranch.get(i).closeThisNode(splitTime);
426 treeIO.writeNode(latestBranch.get(i));
427 }
428
429 /* Link the new root to its first child (the previous root node) */
430 newRootNode.linkNewChild(oldRootNode);
431
432 /* Rebuild a new latestBranch */
433 depth = latestBranch.size();
434 latestBranch = new Vector<CoreNode>();
435 latestBranch.add(newRootNode);
436 for (i = 1; i < depth + 1; i++) {
437 prevNode = latestBranch.get(i - 1);
438 newNode = initNewCoreNode(prevNode.getParentSequenceNumber(),
439 splitTime + 1);
440 prevNode.linkNewChild(newNode);
441 latestBranch.add(newNode);
442 }
443 }
444
445 /**
446 * Add a new empty node to the tree.
3b7f5abe 447 *
a52fde77
AM
448 * @param parentSeqNumber
449 * Sequence number of this node's parent
450 * @param startTime
451 * Start time of the new node
452 * @return The newly created node
453 */
454 private CoreNode initNewCoreNode(int parentSeqNumber, long startTime) {
455 CoreNode newNode = new CoreNode(this, this.nodeCount, parentSeqNumber,
456 startTime);
457 this.nodeCount++;
458
459 /* Update the treeEnd if needed */
460 if (startTime >= this.treeEnd) {
461 this.treeEnd = startTime + 1;
462 }
463 return newNode;
464 }
465
466 /**
467 * Inner method to select the next child of the current node intersecting
468 * the given timestamp. Useful for moving down the tree following one
469 * branch.
3b7f5abe 470 *
a52fde77
AM
471 * @param currentNode
472 * @param t
473 * @return The child node intersecting t
3b7f5abe
AM
474 * @throws ClosedChannelException
475 * If the file channel was closed while we were reading the tree
a52fde77 476 */
3b7f5abe 477 HTNode selectNextChild(CoreNode currentNode, long t) throws ClosedChannelException {
a52fde77
AM
478 assert (currentNode.getNbChildren() > 0);
479 int potentialNextSeqNb = currentNode.getSequenceNumber();
480
481 for (int i = 0; i < currentNode.getNbChildren(); i++) {
482 if (t >= currentNode.getChildStart(i)) {
483 potentialNextSeqNb = currentNode.getChild(i);
484 } else {
485 break;
486 }
487 }
488 /*
489 * Once we exit this loop, we should have found a children to follow. If
490 * we didn't, there's a problem.
491 */
492 assert (potentialNextSeqNb != currentNode.getSequenceNumber());
493
494 /*
495 * Since this code path is quite performance-critical, avoid iterating
496 * through the whole latestBranch array if we know for sure the next
497 * node has to be on disk
498 */
499 if (currentNode.isDone()) {
500 return treeIO.readNodeFromDisk(potentialNextSeqNb);
501 }
502 return treeIO.readNode(potentialNextSeqNb);
503 }
504
505 /**
506 * Helper function to get the size of the "tree header" in the tree-file The
507 * nodes will use this offset to know where they should be in the file. This
508 * should always be a multiple of 4K.
509 */
510 static int getTreeHeaderSize() {
511 return 4096;
512 }
513
514 long getFileSize() {
515 return config.stateFile.length();
516 }
517
3b7f5abe
AM
518 // ------------------------------------------------------------------------
519 // Test/debugging methods
520 // ------------------------------------------------------------------------
a52fde77
AM
521
522 /* Only used for debugging, shouldn't be externalized */
523 @SuppressWarnings("nls")
524 boolean checkNodeIntegrity(HTNode zenode) {
ab604305 525
a52fde77
AM
526 HTNode otherNode;
527 CoreNode node;
ab604305 528 StringBuffer buf = new StringBuffer();
a52fde77
AM
529 boolean ret = true;
530
531 // FIXME /* Only testing Core Nodes for now */
532 if (!(zenode instanceof CoreNode)) {
533 return true;
534 }
535
536 node = (CoreNode) zenode;
537
3b7f5abe
AM
538 try {
539 /*
540 * Test that this node's start and end times match the start of the
541 * first child and the end of the last child, respectively
542 */
543 if (node.getNbChildren() > 0) {
544 otherNode = treeIO.readNode(node.getChild(0));
545 if (node.getNodeStart() != otherNode.getNodeStart()) {
546 buf.append("Start time of node (" + node.getNodeStart() + ") "
547 + "does not match start time of first child " + "("
548 + otherNode.getNodeStart() + "), " + "node #"
ab604305 549 + otherNode.getSequenceNumber() + ")\n");
a52fde77
AM
550 ret = false;
551 }
3b7f5abe
AM
552 if (node.isDone()) {
553 otherNode = treeIO.readNode(node.getLatestChild());
554 if (node.getNodeEnd() != otherNode.getNodeEnd()) {
555 buf.append("End time of node (" + node.getNodeEnd()
556 + ") does not match end time of last child ("
557 + otherNode.getNodeEnd() + ", node #"
558 + otherNode.getSequenceNumber() + ")\n");
559 ret = false;
560 }
561 }
a52fde77 562 }
a52fde77 563
3b7f5abe
AM
564 /*
565 * Test that the childStartTimes[] array matches the real nodes' start
566 * times
567 */
568 for (int i = 0; i < node.getNbChildren(); i++) {
569 otherNode = treeIO.readNode(node.getChild(i));
570 if (otherNode.getNodeStart() != node.getChildStart(i)) {
571 buf.append(" Expected start time of child node #"
572 + node.getChild(i) + ": " + node.getChildStart(i)
573 + "\n" + " Actual start time of node #"
574 + otherNode.getSequenceNumber() + ": "
575 + otherNode.getNodeStart() + "\n");
576 ret = false;
577 }
a52fde77 578 }
3b7f5abe
AM
579
580 } catch (ClosedChannelException e) {
581 e.printStackTrace();
a52fde77
AM
582 }
583
584 if (!ret) {
585 System.out.println("");
586 System.out.println("SHT: Integrity check failed for node #"
587 + node.getSequenceNumber() + ":");
ab604305 588 System.out.println(buf.toString());
a52fde77
AM
589 }
590 return ret;
591 }
592
593 void checkIntegrity() {
3b7f5abe
AM
594 try {
595 for (int i = 0; i < nodeCount; i++) {
596 checkNodeIntegrity(treeIO.readNode(i));
597 }
598 } catch (ClosedChannelException e) {
599 e.printStackTrace();
a52fde77
AM
600 }
601 }
602
603 /* Only used for debugging, shouldn't be externalized */
604 @SuppressWarnings("nls")
605 @Override
606 public String toString() {
607 return "Information on the current tree:\n\n" + "Blocksize: "
608 + config.blockSize + "\n" + "Max nb. of children per node: "
609 + config.maxChildren + "\n" + "Number of nodes: " + nodeCount
610 + "\n" + "Depth of the tree: " + latestBranch.size() + "\n"
611 + "Size of the treefile: " + this.getFileSize() + "\n"
612 + "Root node has sequence number: "
613 + latestBranch.firstElement().getSequenceNumber() + "\n"
614 + "'Latest leaf' has sequence number: "
615 + latestBranch.lastElement().getSequenceNumber();
616 }
617
618 private int curDepth;
619
620 /**
621 * Start at currentNode and print the contents of all its children, in
622 * pre-order. Give the root node in parameter to visit the whole tree, and
623 * have a nice overview.
624 */
625 @SuppressWarnings("nls")
626 private void preOrderPrint(PrintWriter writer, boolean printIntervals,
627 CoreNode currentNode) {
628 /* Only used for debugging, shouldn't be externalized */
629 int i, j;
630 HTNode nextNode;
631
632 writer.println(currentNode.toString());
633 if (printIntervals) {
634 currentNode.debugPrintIntervals(writer);
635 }
636 curDepth++;
637
3b7f5abe
AM
638 try {
639 for (i = 0; i < currentNode.getNbChildren(); i++) {
640 nextNode = treeIO.readNode(currentNode.getChild(i));
641 assert (nextNode instanceof CoreNode); // TODO temporary
642 for (j = 0; j < curDepth - 1; j++) {
643 writer.print(" ");
644 }
645 writer.print("+-");
646 preOrderPrint(writer, printIntervals, (CoreNode) nextNode);
a52fde77 647 }
3b7f5abe
AM
648 } catch (ClosedChannelException e) {
649 e.printStackTrace();
a52fde77
AM
650 }
651 curDepth--;
652 return;
653 }
654
655 /**
656 * Print out the full tree for debugging purposes
3b7f5abe 657 *
a52fde77
AM
658 * @param writer
659 * PrintWriter in which to write the output
660 * @param printIntervals
661 * Says if you want to output the full interval information
662 */
663 void debugPrintFullTree(PrintWriter writer, boolean printIntervals) {
664 /* Only used for debugging, shouldn't be externalized */
665 curDepth = 0;
666 this.preOrderPrint(writer, false, latestBranch.firstElement());
667
668 if (printIntervals) {
669 writer.println("\nDetails of intervals:"); //$NON-NLS-1$
670 curDepth = 0;
671 this.preOrderPrint(writer, true, latestBranch.firstElement());
672 }
673 writer.println('\n');
674 }
675
676}
This page took 0.064051 seconds and 5 git commands to generate.