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