tmf: Add a proper toString() to history tree intervals
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.core / src / org / eclipse / linuxtools / tmf / core / statesystem / backend / 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
13package org.eclipse.linuxtools.tmf.core.statesystem.backend.historytree;
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
24import org.eclipse.linuxtools.tmf.core.statesystem.TimeRangeException;
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;
118 long ts;
119
120 /* Java I/O mumbo jumbo... */
121 if ((!existingStateFile.exists()) || existingStateFile.length() <= 0) {
122 throw new IOException("Invalid state file selected"); //$NON-NLS-1$
123 }
124
125 FileInputStream fis = new FileInputStream(existingStateFile);
126 ByteBuffer buffer = ByteBuffer.allocate(getTreeHeaderSize());
127 FileChannel fc = fis.getChannel();
128 buffer.order(ByteOrder.LITTLE_ENDIAN);
129 buffer.clear();
130 fc.read(buffer);
131 buffer.flip();
132
133 /*
134 * Check the magic number,to make sure we're opening the right type of
135 * file
136 */
137 res = buffer.getInt();
138 if (res != HISTORY_FILE_MAGIC_NUMBER) {
ab604305
AM
139 throw new IOException(
140 "Selected file does not look like a History Tree file"); //$NON-NLS-1$
a52fde77
AM
141 }
142
143 res = buffer.getInt(); /* Major version number */
144 if (res != MAJOR_VERSION) {
145 throw new IOException("Select History Tree file is of an older " //$NON-NLS-1$
146 + "format. Please use a previous version of " //$NON-NLS-1$
147 + "the parser to open it."); //$NON-NLS-1$
148 }
149
150 res = buffer.getInt(); /* Minor version number */
151
152 bs = buffer.getInt(); /* Block Size */
153 maxc = buffer.getInt(); /* Max nb of children per node */
154
155 this.nodeCount = buffer.getInt();
156 rootNodeSeqNb = buffer.getInt();
157
158 /* Go read the start time, which is also the root node's start time */
159 // TODO maybe make this part less ugly, as we need treeStart to build
160 // the SHTConfig object, but we need that object for new SHT_IO()
161 // and rebuildLatestBranch() ...
162 fc.position(getTreeHeaderSize() + (long) rootNodeSeqNb * bs);
163 buffer = ByteBuffer.allocate(8);
164 buffer.order(ByteOrder.LITTLE_ENDIAN);
165 buffer.clear();
166 res = fc.read(buffer);
167 assert (res == 8);
168 buffer.flip();
169 ts = buffer.getLong();
170
171 this.config = new HTConfig(existingStateFile, bs, maxc, ts);
172 fis.close();
173 /*
174 * FIXME We close fis here and the TreeIO will then reopen the same
175 * file, not extremely elegant. But how to pass the information here to
176 * the SHT otherwise?
177 */
178 this.treeIO = new HT_IO(this, false);
179
180 rebuildLatestBranch(rootNodeSeqNb);
a52fde77
AM
181 this.treeEnd = latestBranch.firstElement().getNodeEnd();
182 }
183
184 /**
185 * "Save" the tree to disk. This method will cause the treeIO object to
186 * commit all nodes to disk and then return the RandomAccessFile descriptor
187 * so the Tree object can save its configuration into the header of the
188 * file.
189 *
190 * @param requestedEndTime
191 */
192 void closeTree() {
193 FileChannel fc;
194 ByteBuffer buffer;
195 int i, res;
196
197 /* Close off the latest branch of the tree */
198 for (i = 0; i < latestBranch.size(); i++) {
199 latestBranch.get(i).closeThisNode(treeEnd);
200 treeIO.writeNode(latestBranch.get(i));
201 }
202
203 /* Only use this for debugging purposes, it's VERY slow! */
204 // this.checkIntegrity();
205
206 fc = treeIO.getFcOut();
207 buffer = ByteBuffer.allocate(getTreeHeaderSize());
208 buffer.order(ByteOrder.LITTLE_ENDIAN);
209 buffer.clear();
210
211 /* Save the config of the tree to the header of the file */
212 try {
213 fc.position(0);
214
215 buffer.putInt(HISTORY_FILE_MAGIC_NUMBER);
216
217 buffer.putInt(MAJOR_VERSION);
218 buffer.putInt(MINOR_VERSION);
219
220 buffer.putInt(config.blockSize);
221 buffer.putInt(config.maxChildren);
222
223 buffer.putInt(nodeCount);
224
225 /* root node seq. nb */
226 buffer.putInt(latestBranch.firstElement().getSequenceNumber());
227
228 buffer.flip();
229 res = fc.write(buffer);
230 assert (res <= getTreeHeaderSize());
231 /* done writing the file header */
232
233 } catch (IOException e) {
234 /* We should have any problems at this point... */
235 e.printStackTrace();
236 }
237 return;
238 }
239
240 /**
241 * @name Accessors
242 */
ab604305 243
a52fde77
AM
244 long getTreeStart() {
245 return config.treeStart;
246 }
247
248 long getTreeEnd() {
249 return treeEnd;
250 }
251
252 int getNodeCount() {
253 return nodeCount;
254 }
255
256 HT_IO getTreeIO() {
257 return treeIO;
258 }
259
260 /**
261 * Rebuild the latestBranch "cache" object by reading the nodes from disk
262 * (When we are opening an existing file on disk and want to append to it,
263 * for example).
264 *
265 * @param rootNodeSeqNb
266 * The sequence number of the root node, so we know where to
267 * start
268 */
269 private void rebuildLatestBranch(int rootNodeSeqNb) {
270 HTNode nextChildNode;
271
272 this.latestBranch = new Vector<CoreNode>();
273
274 nextChildNode = treeIO.readNodeFromDisk(rootNodeSeqNb);
275 latestBranch.add((CoreNode) nextChildNode);
276 while (latestBranch.lastElement().getNbChildren() > 0) {
277 nextChildNode = treeIO.readNodeFromDisk(latestBranch.lastElement().getLatestChild());
278 latestBranch.add((CoreNode) nextChildNode);
279 }
280 }
281
282 /**
283 * Insert an interval in the tree
284 *
285 * @param interval
286 */
287 void insertInterval(HTInterval interval) throws TimeRangeException {
288 if (interval.getStartTime() < config.treeStart) {
289 throw new TimeRangeException();
290 }
291 tryInsertAtNode(interval, latestBranch.size() - 1);
292 }
293
294 /**
295 * Inner method to find in which node we should add the interval.
296 *
297 * @param interval
298 * The interval to add to the tree
299 * @param indexOfNode
300 * The index *in the latestBranch* where we are trying the
301 * insertion
302 */
303 private void tryInsertAtNode(HTInterval interval, int indexOfNode) {
304 HTNode targetNode = latestBranch.get(indexOfNode);
305
306 /* Verify if there is enough room in this node to store this interval */
307 if (interval.getIntervalSize() > targetNode.getNodeFreeSpace()) {
308 /* Nope, not enough room. Insert in a new sibling instead. */
309 addSiblingNode(indexOfNode);
310 tryInsertAtNode(interval, latestBranch.size() - 1);
311 return;
312 }
313
314 /* Make sure the interval time range fits this node */
315 if (interval.getStartTime() < targetNode.getNodeStart()) {
316 /*
317 * No, this interval starts before the startTime of this node. We
318 * need to check recursively in parents if it can fit.
319 */
320 assert (indexOfNode >= 1);
321 tryInsertAtNode(interval, indexOfNode - 1);
322 return;
323 }
324
325 /*
326 * Ok, there is room, and the interval fits in this time slot. Let's add
327 * it.
328 */
329 targetNode.addInterval(interval);
330
331 /* Update treeEnd if needed */
332 if (interval.getEndTime() > this.treeEnd) {
333 this.treeEnd = interval.getEndTime();
334 }
335 return;
336 }
337
338 /**
339 * Method to add a sibling to any node in the latest branch. This will add
340 * children back down to the leaf level, if needed.
341 *
342 * @param indexOfNode
343 * The index in latestBranch where we start adding
344 */
345 private void addSiblingNode(int indexOfNode) {
346 int i;
347 CoreNode newNode, prevNode;
348 long splitTime = treeEnd;
349
350 assert (indexOfNode < latestBranch.size());
351
352 /* Check if we need to add a new root node */
353 if (indexOfNode == 0) {
354 addNewRootNode();
355 return;
356 }
357
358 /* Check if we can indeed add a child to the target parent */
359 if (latestBranch.get(indexOfNode - 1).getNbChildren() == config.maxChildren) {
360 /* If not, add a branch starting one level higher instead */
361 addSiblingNode(indexOfNode - 1);
362 return;
363 }
364
365 /* Split off the new branch from the old one */
366 for (i = indexOfNode; i < latestBranch.size(); i++) {
367 latestBranch.get(i).closeThisNode(splitTime);
368 treeIO.writeNode(latestBranch.get(i));
369
370 prevNode = latestBranch.get(i - 1);
371 newNode = initNewCoreNode(prevNode.getSequenceNumber(),
372 splitTime + 1);
373 prevNode.linkNewChild(newNode);
374
375 latestBranch.set(i, newNode);
376 }
377 return;
378 }
379
380 /**
381 * Similar to the previous method, except here we rebuild a completely new
382 * latestBranch
383 */
384 private void addNewRootNode() {
385 int i, depth;
386 CoreNode oldRootNode, newRootNode, newNode, prevNode;
387 long splitTime = this.treeEnd;
388
389 oldRootNode = latestBranch.firstElement();
390 newRootNode = initNewCoreNode(-1, config.treeStart);
391
392 /* Tell the old root node that it isn't root anymore */
393 oldRootNode.setParentSequenceNumber(newRootNode.getSequenceNumber());
394
395 /* Close off the whole current latestBranch */
396 for (i = 0; i < latestBranch.size(); i++) {
397 latestBranch.get(i).closeThisNode(splitTime);
398 treeIO.writeNode(latestBranch.get(i));
399 }
400
401 /* Link the new root to its first child (the previous root node) */
402 newRootNode.linkNewChild(oldRootNode);
403
404 /* Rebuild a new latestBranch */
405 depth = latestBranch.size();
406 latestBranch = new Vector<CoreNode>();
407 latestBranch.add(newRootNode);
408 for (i = 1; i < depth + 1; i++) {
409 prevNode = latestBranch.get(i - 1);
410 newNode = initNewCoreNode(prevNode.getParentSequenceNumber(),
411 splitTime + 1);
412 prevNode.linkNewChild(newNode);
413 latestBranch.add(newNode);
414 }
415 }
416
417 /**
418 * Add a new empty node to the tree.
419 *
420 * @param parentSeqNumber
421 * Sequence number of this node's parent
422 * @param startTime
423 * Start time of the new node
424 * @return The newly created node
425 */
426 private CoreNode initNewCoreNode(int parentSeqNumber, long startTime) {
427 CoreNode newNode = new CoreNode(this, this.nodeCount, parentSeqNumber,
428 startTime);
429 this.nodeCount++;
430
431 /* Update the treeEnd if needed */
432 if (startTime >= this.treeEnd) {
433 this.treeEnd = startTime + 1;
434 }
435 return newNode;
436 }
437
438 /**
439 * Inner method to select the next child of the current node intersecting
440 * the given timestamp. Useful for moving down the tree following one
441 * branch.
442 *
443 * @param currentNode
444 * @param t
445 * @return The child node intersecting t
446 */
447 HTNode selectNextChild(CoreNode currentNode, long t) {
448 assert (currentNode.getNbChildren() > 0);
449 int potentialNextSeqNb = currentNode.getSequenceNumber();
450
451 for (int i = 0; i < currentNode.getNbChildren(); i++) {
452 if (t >= currentNode.getChildStart(i)) {
453 potentialNextSeqNb = currentNode.getChild(i);
454 } else {
455 break;
456 }
457 }
458 /*
459 * Once we exit this loop, we should have found a children to follow. If
460 * we didn't, there's a problem.
461 */
462 assert (potentialNextSeqNb != currentNode.getSequenceNumber());
463
464 /*
465 * Since this code path is quite performance-critical, avoid iterating
466 * through the whole latestBranch array if we know for sure the next
467 * node has to be on disk
468 */
469 if (currentNode.isDone()) {
470 return treeIO.readNodeFromDisk(potentialNextSeqNb);
471 }
472 return treeIO.readNode(potentialNextSeqNb);
473 }
474
475 /**
476 * Helper function to get the size of the "tree header" in the tree-file The
477 * nodes will use this offset to know where they should be in the file. This
478 * should always be a multiple of 4K.
479 */
480 static int getTreeHeaderSize() {
481 return 4096;
482 }
483
484 long getFileSize() {
485 return config.stateFile.length();
486 }
487
488 /**
489 * @name Test/debugging functions
490 */
491
492 /* Only used for debugging, shouldn't be externalized */
493 @SuppressWarnings("nls")
494 boolean checkNodeIntegrity(HTNode zenode) {
ab604305 495
a52fde77
AM
496 HTNode otherNode;
497 CoreNode node;
ab604305 498 StringBuffer buf = new StringBuffer();
a52fde77
AM
499 boolean ret = true;
500
501 // FIXME /* Only testing Core Nodes for now */
502 if (!(zenode instanceof CoreNode)) {
503 return true;
504 }
505
506 node = (CoreNode) zenode;
507
508 /*
509 * Test that this node's start and end times match the start of the
510 * first child and the end of the last child, respectively
511 */
512 if (node.getNbChildren() > 0) {
513 otherNode = treeIO.readNode(node.getChild(0));
514 if (node.getNodeStart() != otherNode.getNodeStart()) {
ab604305 515 buf.append("Start time of node (" + node.getNodeStart() + ") "
a52fde77
AM
516 + "does not match start time of first child " + "("
517 + otherNode.getNodeStart() + "), " + "node #"
ab604305 518 + otherNode.getSequenceNumber() + ")\n");
a52fde77
AM
519 ret = false;
520 }
521 if (node.isDone()) {
522 otherNode = treeIO.readNode(node.getLatestChild());
523 if (node.getNodeEnd() != otherNode.getNodeEnd()) {
ab604305 524 buf.append("End time of node (" + node.getNodeEnd()
a52fde77
AM
525 + ") does not match end time of last child ("
526 + otherNode.getNodeEnd() + ", node #"
ab604305 527 + otherNode.getSequenceNumber() + ")\n");
a52fde77
AM
528 ret = false;
529 }
530 }
531 }
532
533 /*
534 * Test that the childStartTimes[] array matches the real nodes' start
535 * times
536 */
537 for (int i = 0; i < node.getNbChildren(); i++) {
538 otherNode = treeIO.readNode(node.getChild(i));
539 if (otherNode.getNodeStart() != node.getChildStart(i)) {
ab604305 540 buf.append(" Expected start time of child node #"
a52fde77
AM
541 + node.getChild(i) + ": " + node.getChildStart(i)
542 + "\n" + " Actual start time of node #"
543 + otherNode.getSequenceNumber() + ": "
ab604305 544 + otherNode.getNodeStart() + "\n");
a52fde77
AM
545 ret = false;
546 }
547 }
548
549 if (!ret) {
550 System.out.println("");
551 System.out.println("SHT: Integrity check failed for node #"
552 + node.getSequenceNumber() + ":");
ab604305 553 System.out.println(buf.toString());
a52fde77
AM
554 }
555 return ret;
556 }
557
558 void checkIntegrity() {
559 for (int i = 0; i < nodeCount; i++) {
560 checkNodeIntegrity(treeIO.readNode(i));
561 }
562 }
563
564 /* Only used for debugging, shouldn't be externalized */
565 @SuppressWarnings("nls")
566 @Override
567 public String toString() {
568 return "Information on the current tree:\n\n" + "Blocksize: "
569 + config.blockSize + "\n" + "Max nb. of children per node: "
570 + config.maxChildren + "\n" + "Number of nodes: " + nodeCount
571 + "\n" + "Depth of the tree: " + latestBranch.size() + "\n"
572 + "Size of the treefile: " + this.getFileSize() + "\n"
573 + "Root node has sequence number: "
574 + latestBranch.firstElement().getSequenceNumber() + "\n"
575 + "'Latest leaf' has sequence number: "
576 + latestBranch.lastElement().getSequenceNumber();
577 }
578
579 private int curDepth;
580
581 /**
582 * Start at currentNode and print the contents of all its children, in
583 * pre-order. Give the root node in parameter to visit the whole tree, and
584 * have a nice overview.
585 */
586 @SuppressWarnings("nls")
587 private void preOrderPrint(PrintWriter writer, boolean printIntervals,
588 CoreNode currentNode) {
589 /* Only used for debugging, shouldn't be externalized */
590 int i, j;
591 HTNode nextNode;
592
593 writer.println(currentNode.toString());
594 if (printIntervals) {
595 currentNode.debugPrintIntervals(writer);
596 }
597 curDepth++;
598
599 for (i = 0; i < currentNode.getNbChildren(); i++) {
600 nextNode = treeIO.readNode(currentNode.getChild(i));
601 assert (nextNode instanceof CoreNode); // TODO temporary
602 for (j = 0; j < curDepth - 1; j++) {
603 writer.print(" ");
604 }
605 writer.print("+-");
606 preOrderPrint(writer, printIntervals, (CoreNode) nextNode);
607 }
608 curDepth--;
609 return;
610 }
611
612 /**
613 * Print out the full tree for debugging purposes
614 *
615 * @param writer
616 * PrintWriter in which to write the output
617 * @param printIntervals
618 * Says if you want to output the full interval information
619 */
620 void debugPrintFullTree(PrintWriter writer, boolean printIntervals) {
621 /* Only used for debugging, shouldn't be externalized */
622 curDepth = 0;
623 this.preOrderPrint(writer, false, latestBranch.firstElement());
624
625 if (printIntervals) {
626 writer.println("\nDetails of intervals:"); //$NON-NLS-1$
627 curDepth = 0;
628 this.preOrderPrint(writer, true, latestBranch.firstElement());
629 }
630 writer.println('\n');
631 }
632
633}
This page took 0.048972 seconds and 5 git commands to generate.