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