tmf: Add Guava dependency
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.core / src / org / eclipse / linuxtools / internal / tmf / core / statesystem / backends / historytree / HTNode.java
CommitLineData
a52fde77 1/*******************************************************************************
60ae41e1 2 * Copyright (c) 2012, 2014 Ericsson
a52fde77
AM
3 * Copyright (c) 2010, 2011 École Polytechnique de Montréal
4 * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
6f4e8ec0 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
6f4e8ec0 10 *
a52fde77
AM
11 *******************************************************************************/
12
f9a76cac 13package org.eclipse.linuxtools.internal.tmf.core.statesystem.backends.historytree;
a52fde77
AM
14
15import java.io.IOException;
16import java.io.PrintWriter;
17import java.nio.ByteBuffer;
18import java.nio.ByteOrder;
19import java.nio.channels.FileChannel;
20import java.util.ArrayList;
21import java.util.Collections;
22import java.util.List;
62197b87 23import java.util.concurrent.locks.ReentrantReadWriteLock;
a52fde77 24
6d08acca 25import org.eclipse.linuxtools.tmf.core.exceptions.TimeRangeException;
a52fde77 26import org.eclipse.linuxtools.tmf.core.interval.ITmfStateInterval;
a52fde77
AM
27import org.eclipse.linuxtools.tmf.core.statevalue.TmfStateValue;
28
29/**
30 * The base class for all the types of nodes that go in the History Tree.
6f4e8ec0 31 *
ffd0aa67 32 * @author Alexandre Montplaisir
a52fde77 33 */
8d47cc34 34public abstract class HTNode {
a52fde77 35
cb42195c
AM
36 /**
37 * Size of an entry in the data section.
38 *
62197b87 39 * <pre>
cb42195c
AM
40 * 16 2 x Timevalue/long (interval start + end)
41 * + 4 int (key)
42 * + 1 byte (type)
43 * + 4 int (valueOffset)
62197b87 44 * </pre>
cb42195c
AM
45 */
46 protected static final int DATA_ENTRY_SIZE = 25;
47
ffd0aa67
EB
48 /* Configuration of the History Tree to which belongs this node */
49 private final HTConfig config;
a52fde77
AM
50
51 /* Time range of this node */
52 private final long nodeStart;
53 private long nodeEnd;
54
55 /* Sequence number = position in the node section of the file */
56 private final int sequenceNumber;
57 private int parentSequenceNumber; /* = -1 if this node is the root node */
58
59 /* Where the Strings section begins (from the start of the node */
60 private int stringSectionOffset;
61
045badfe
AM
62 /* True if this node was read from disk (meaning its end time is now fixed) */
63 private volatile boolean isOnDisk;
a52fde77
AM
64
65 /* Vector containing all the intervals contained in this node */
cb42195c 66 private final List<HTInterval> intervals;
a52fde77 67
62197b87
AM
68 /* Lock used to protect the accesses to intervals, nodeEnd and such */
69 private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(false);
70
8d47cc34
AM
71 /**
72 * Constructor
73 *
74 * @param config
75 * Configuration of the History Tree
76 * @param seqNumber
77 * The (unique) sequence number assigned to this particular node
78 * @param parentSeqNumber
79 * The sequence number of this node's parent node
80 * @param start
81 * The earliest timestamp stored in this node
82 */
83 protected HTNode(HTConfig config, int seqNumber, int parentSeqNumber, long start) {
ffd0aa67 84 this.config = config;
a52fde77
AM
85 this.nodeStart = start;
86 this.sequenceNumber = seqNumber;
87 this.parentSequenceNumber = parentSeqNumber;
88
ffd0aa67 89 this.stringSectionOffset = config.getBlockSize();
045badfe 90 this.isOnDisk = false;
a4524c1b 91 this.intervals = new ArrayList<>();
a52fde77
AM
92 }
93
94 /**
8d47cc34
AM
95 * Reader factory method. Build a Node object (of the right type) by reading
96 * a block in the file.
6f4e8ec0 97 *
ffd0aa67
EB
98 * @param config
99 * Configuration of the History Tree
a52fde77
AM
100 * @param fc
101 * FileChannel to the history file, ALREADY SEEKED at the start
102 * of the node.
8d47cc34 103 * @return The node object
a52fde77 104 * @throws IOException
8d47cc34 105 * If there was an error reading from the file channel
a52fde77 106 */
8d47cc34 107 public static final HTNode readNode(HTConfig config, FileChannel fc)
a52fde77
AM
108 throws IOException {
109 HTNode newNode = null;
110 int res, i;
111
ffd0aa67 112 ByteBuffer buffer = ByteBuffer.allocate(config.getBlockSize());
a52fde77
AM
113 buffer.order(ByteOrder.LITTLE_ENDIAN);
114 buffer.clear();
115 res = fc.read(buffer);
ffd0aa67 116 assert (res == config.getBlockSize());
a52fde77
AM
117 buffer.flip();
118
119 /* Read the common header part */
120 byte type = buffer.get();
121 long start = buffer.getLong();
122 long end = buffer.getLong();
123 int seqNb = buffer.getInt();
124 int parentSeqNb = buffer.getInt();
125 int intervalCount = buffer.getInt();
126 int stringSectionOffset = buffer.getInt();
045badfe 127 buffer.get(); // TODO Used to be "isDone", to be removed from the header
a52fde77
AM
128
129 /* Now the rest of the header depends on the node type */
130 switch (type) {
131 case 1:
132 /* Core nodes */
ffd0aa67 133 newNode = new CoreNode(config, seqNb, parentSeqNb, start);
a52fde77
AM
134 newNode.readSpecificHeader(buffer);
135 break;
136
137 // TODO implement other node types
138 // case 2:
139 // /* Leaf nodes */
140 //
a52fde77
AM
141 //
142 //
143 // case 3:
144 // /* "Claudette" (extended) nodes */
145 //
a52fde77
AM
146
147 default:
148 /* Unrecognized node type */
149 throw new IOException();
150 }
151
152 /*
153 * At this point, we should be done reading the header and 'buffer'
154 * should only have the intervals left
155 */
156 for (i = 0; i < intervalCount; i++) {
157 newNode.intervals.add(HTInterval.readFrom(buffer));
158 }
159
160 /* Assign the node's other information we have read previously */
161 newNode.nodeEnd = end;
162 newNode.stringSectionOffset = stringSectionOffset;
045badfe 163 newNode.isOnDisk = true;
a52fde77
AM
164
165 return newNode;
166 }
167
8d47cc34
AM
168 /**
169 * Write this node to the given file channel.
170 *
171 * @param fc
172 * The file channel to write to (should be sought to be correct
173 * position)
174 * @throws IOException
175 * If there was an error writing
176 */
177 public final void writeSelf(FileChannel fc) throws IOException {
a52fde77 178 /*
62197b87
AM
179 * Yes, we are taking the *read* lock here, because we are reading the
180 * information in the node to write it to disk.
a52fde77 181 */
62197b87
AM
182 rwl.readLock().lock();
183 try {
184 final int blockSize = config.getBlockSize();
185 int curStringsEntryEndPos = blockSize;
186
187 ByteBuffer buffer = ByteBuffer.allocate(blockSize);
188 buffer.order(ByteOrder.LITTLE_ENDIAN);
189 buffer.clear();
190
191 /* Write the common header part */
192 buffer.put(this.getNodeType());
193 buffer.putLong(nodeStart);
194 buffer.putLong(nodeEnd);
195 buffer.putInt(sequenceNumber);
196 buffer.putInt(parentSequenceNumber);
197 buffer.putInt(intervals.size());
198 buffer.putInt(stringSectionOffset);
199 buffer.put((byte) 1); // TODO Used to be "isDone", to be removed from header
200
201 /* Now call the inner method to write the specific header part */
202 this.writeSpecificHeader(buffer);
203
204 /* Back to us, we write the intervals */
205 for (HTInterval interval : intervals) {
206 int size = interval.writeInterval(buffer, curStringsEntryEndPos);
207 curStringsEntryEndPos -= size;
208 }
a52fde77 209
62197b87
AM
210 /*
211 * Write padding between the end of the Data section and the start
212 * of the Strings section (needed to fill the node in case there is
213 * no Strings section)
214 */
215 while (buffer.position() < stringSectionOffset) {
216 buffer.put((byte) 0);
217 }
a52fde77 218
62197b87
AM
219 /*
220 * If the offsets were right, the size of the Strings section should
221 * be == to the expected size
222 */
223 assert (curStringsEntryEndPos == stringSectionOffset);
a52fde77 224
62197b87 225 /* Finally, write everything in the Buffer to disk */
a52fde77 226
62197b87
AM
227 // if we don't do this, flip() will lose what's after.
228 buffer.position(blockSize);
229
230 buffer.flip();
231 int res = fc.write(buffer);
232 assert (res == blockSize);
233
234 } finally {
235 rwl.readLock().unlock();
236 }
045badfe 237 isOnDisk = true;
cb42195c
AM
238 }
239
240 // ------------------------------------------------------------------------
241 // Accessors
242 // ------------------------------------------------------------------------
243
8d47cc34
AM
244 /**
245 * Retrieve the history tree configuration used for this node.
246 *
247 * @return The history tree config
248 */
249 protected HTConfig getConfig() {
ffd0aa67 250 return config;
a52fde77
AM
251 }
252
8d47cc34
AM
253 /**
254 * Get the start time of this node.
255 *
256 * @return The start time of this node
257 */
258 public long getNodeStart() {
a52fde77
AM
259 return nodeStart;
260 }
261
8d47cc34
AM
262 /**
263 * Get the end time of this node.
264 *
265 * @return The end time of this node
266 */
267 public long getNodeEnd() {
045badfe 268 if (this.isOnDisk) {
a52fde77
AM
269 return nodeEnd;
270 }
271 return 0;
272 }
273
8d47cc34
AM
274 /**
275 * Get the sequence number of this node.
276 *
277 * @return The sequence number of this node
278 */
279 public int getSequenceNumber() {
a52fde77
AM
280 return sequenceNumber;
281 }
282
8d47cc34
AM
283 /**
284 * Get the sequence number of this node's parent.
285 *
286 * @return The parent sequence number
287 */
288 public int getParentSequenceNumber() {
a52fde77
AM
289 return parentSequenceNumber;
290 }
291
292 /**
293 * Change this node's parent. Used when we create a new root node for
294 * example.
8d47cc34
AM
295 *
296 * @param newParent
297 * The sequence number of the node that is the new parent
a52fde77 298 */
8d47cc34 299 public void setParentSequenceNumber(int newParent) {
a52fde77
AM
300 parentSequenceNumber = newParent;
301 }
302
8d47cc34
AM
303 /**
304 * Return if this node is "done" (full and written to disk).
305 *
306 * @return If this node is done or not
307 */
045badfe
AM
308 public boolean isOnDisk() {
309 return isOnDisk;
a52fde77
AM
310 }
311
312 /**
313 * Add an interval to this node
6f4e8ec0 314 *
a52fde77 315 * @param newInterval
8d47cc34 316 * Interval to add to this node
a52fde77 317 */
8d47cc34 318 public void addInterval(HTInterval newInterval) {
62197b87
AM
319 rwl.writeLock().lock();
320 try {
321 /* Just in case, should be checked before even calling this function */
322 assert (newInterval.getIntervalSize() <= this.getNodeFreeSpace());
a52fde77 323
62197b87 324 intervals.add(newInterval);
a52fde77 325
62197b87
AM
326 /* Update the in-node offset "pointer" */
327 stringSectionOffset -= (newInterval.getStringsEntrySize());
328 } finally {
329 rwl.writeLock().unlock();
330 }
a52fde77
AM
331 }
332
333 /**
334 * We've received word from the containerTree that newest nodes now exist to
335 * our right. (Puts isDone = true and sets the endtime)
6f4e8ec0 336 *
a52fde77
AM
337 * @param endtime
338 * The nodeEnd time that the node will have
a52fde77 339 */
8d47cc34 340 public void closeThisNode(long endtime) {
62197b87
AM
341 rwl.writeLock().lock();
342 try {
343 assert (endtime >= this.nodeStart);
344
345 if (intervals.size() > 0) {
346 /*
347 * Sort the intervals by ascending order of their end time. This
348 * speeds up lookups a bit
349 */
350 Collections.sort(intervals);
351
352 /*
353 * Make sure there are no intervals in this node with their
354 * EndTime > the one requested. Only need to check the last one
355 * since they are now sorted
356 */
357 assert (endtime >= intervals.get(intervals.size() - 1).getEndTime());
358 }
a52fde77 359
62197b87
AM
360 this.nodeEnd = endtime;
361 } finally {
362 rwl.writeLock().unlock();
a52fde77 363 }
a52fde77
AM
364 }
365
366 /**
367 * The method to fill up the stateInfo (passed on from the Current State
368 * Tree when it does a query on the SHT). We'll replace the data in that
369 * vector with whatever relevant we can find from this node
6f4e8ec0 370 *
a52fde77
AM
371 * @param stateInfo
372 * The same stateInfo that comes from SHT's doQuery()
373 * @param t
374 * The timestamp for which the query is for. Only return
375 * intervals that intersect t.
376 * @throws TimeRangeException
8d47cc34 377 * If 't' is invalid
a52fde77 378 */
8d47cc34 379 public void writeInfoFromNode(List<ITmfStateInterval> stateInfo, long t)
a52fde77 380 throws TimeRangeException {
62197b87
AM
381 /* This is from a state system query, we are "reading" this node */
382 rwl.readLock().lock();
383 try {
384 if (intervals.size() == 0) {
385 return;
386 }
387 int startIndex = getStartIndexFor(t);
388
389 for (int i = startIndex; i < intervals.size(); i++) {
390 /*
391 * Now we only have to compare the Start times, since we now the
392 * End times necessarily fit
393 */
394 if (intervals.get(i).getStartTime() <= t) {
395 stateInfo.set(intervals.get(i).getAttribute(), intervals.get(i));
396 }
a52fde77 397 }
62197b87
AM
398 } finally {
399 rwl.readLock().unlock();
a52fde77 400 }
a52fde77
AM
401 }
402
403 /**
404 * Get a single Interval from the information in this node If the
405 * key/timestamp pair cannot be found, we return null.
6f4e8ec0 406 *
a52fde77 407 * @param key
8d47cc34 408 * The attribute quark to look for
a52fde77 409 * @param t
8d47cc34 410 * The timestamp
a52fde77
AM
411 * @return The Interval containing the information we want, or null if it
412 * wasn't found
8d47cc34 413 * @throws TimeRangeException If 't' is invalid
a52fde77 414 */
8d47cc34 415 public HTInterval getRelevantInterval(int key, long t) throws TimeRangeException {
62197b87
AM
416 rwl.readLock().lock();
417 try {
418 if (intervals.size() == 0) {
419 return null;
420 }
a52fde77 421
62197b87 422 int startIndex = getStartIndexFor(t);
a52fde77 423
62197b87
AM
424 for (int i = startIndex; i < intervals.size(); i++) {
425 HTInterval curInterval = intervals.get(i);
426 if (curInterval.getAttribute() == key
427 && curInterval.getStartTime() <= t
428 && curInterval.getEndTime() >= t) {
429 return curInterval;
430 }
a52fde77 431 }
62197b87
AM
432 /* We didn't find the relevant information in this node */
433 return null;
434
435 } finally {
436 rwl.readLock().unlock();
a52fde77 437 }
a52fde77
AM
438 }
439
440 private int getStartIndexFor(long t) throws TimeRangeException {
62197b87 441 /* Should only be called by methods with the readLock taken */
a52fde77
AM
442 /*
443 * Since the intervals are sorted by end time, we can skip all the ones
444 * at the beginning whose end times are smaller than 't'. Java does
445 * provides a .binarySearch method, but its API is quite weird...
446 */
62197b87
AM
447 HTInterval dummy = new HTInterval(0, t, 0, TmfStateValue.nullValue());
448 int index = Collections.binarySearch(intervals, dummy);
a52fde77
AM
449
450 if (index < 0) {
451 /*
452 * .binarySearch returns a negative number if the exact value was
453 * not found. Here we just want to know where to start searching, we
454 * don't care if the value is exact or not.
455 */
456 index = -index - 1;
457
458 }
459
460 /* Sometimes binarySearch yields weird stuff... */
461 if (index < 0) {
462 index = 0;
463 }
464 if (index >= intervals.size()) {
465 index = intervals.size() - 1;
466 }
467
468 /*
469 * Another API quirkiness, the returned index is the one of the *last*
470 * element of a series of equal endtimes, which happens sometimes. We
471 * want the *first* element of such a series, to read through them
472 * again.
473 */
474 while (index > 0
475 && intervals.get(index - 1).compareTo(intervals.get(index)) == 0) {
476 index--;
477 }
a52fde77
AM
478
479 return index;
480 }
481
62197b87
AM
482
483 /**
484 * <pre>
485 * 1 - byte (type)
486 * 16 - 2x long (start time, end time)
487 * 16 - 4x int (seq number, parent seq number, intervalcount,
488 * strings section pos.)
489 * 1 - byte (done or not)
490 * </pre>
491 */
492 private static final int COMMON_HEADER_SIZE = 34;
493
494 /**
495 * Return the total header size of this node (will depend on the node type).
496 *
497 * @return The total header size
498 */
499 public final int getTotalHeaderSize() {
500 return COMMON_HEADER_SIZE + getSpecificHeaderSize();
501 }
502
a52fde77
AM
503 /**
504 * @return The offset, within the node, where the Data section ends
505 */
506 private int getDataSectionEndOffset() {
cb42195c 507 return this.getTotalHeaderSize() + HTNode.DATA_ENTRY_SIZE * intervals.size();
a52fde77
AM
508 }
509
510 /**
511 * Returns the free space in the node, which is simply put, the
512 * stringSectionOffset - dataSectionOffset
8d47cc34
AM
513 *
514 * @return The amount of free space in the node (in bytes)
a52fde77 515 */
8d47cc34 516 public int getNodeFreeSpace() {
62197b87
AM
517 rwl.readLock().lock();
518 int ret = stringSectionOffset - this.getDataSectionEndOffset();
519 rwl.readLock().unlock();
520
521 return ret;
a52fde77
AM
522 }
523
524 /**
8d47cc34 525 * Returns the current space utilization of this node, as a percentage.
a52fde77 526 * (used space / total usable space, which excludes the header)
8d47cc34
AM
527 *
528 * @return The percentage (value between 0 and 100) of space utilization in
529 * in this node.
a52fde77 530 */
8d47cc34 531 public long getNodeUsagePercent() {
62197b87
AM
532 rwl.readLock().lock();
533 try {
534 final int blockSize = config.getBlockSize();
535 float freePercent = (float) this.getNodeFreeSpace()
536 / (float) (blockSize - this.getTotalHeaderSize())
537 * 100F;
538 return (long) (100L - freePercent);
539
540 } finally {
541 rwl.readLock().unlock();
542 }
a52fde77
AM
543 }
544
a52fde77
AM
545 /**
546 * @name Debugging functions
547 */
548
549 @SuppressWarnings("nls")
550 @Override
551 public String toString() {
552 /* Only used for debugging, shouldn't be externalized */
553 StringBuffer buf = new StringBuffer("Node #" + sequenceNumber + ", ");
554 buf.append(this.toStringSpecific());
8d47cc34 555 buf.append(intervals.size() + " intervals (" + this.getNodeUsagePercent()
a52fde77
AM
556 + "% used), ");
557
558 buf.append("[" + this.nodeStart + " - ");
045badfe 559 if (this.isOnDisk) {
a52fde77
AM
560 buf = buf.append("" + this.nodeEnd + "]");
561 } else {
562 buf = buf.append("...]");
563 }
564 return buf.toString();
565 }
566
567 /**
568 * Debugging function that prints out the contents of this node
6f4e8ec0 569 *
a52fde77
AM
570 * @param writer
571 * PrintWriter in which we will print the debug output
572 */
573 @SuppressWarnings("nls")
8d47cc34 574 public void debugPrintIntervals(PrintWriter writer) {
a52fde77
AM
575 /* Only used for debugging, shouldn't be externalized */
576 writer.println("Node #" + sequenceNumber + ":");
577
578 /* Array of children */
579 if (this.getNodeType() == 1) { /* Only Core Nodes can have children */
580 CoreNode thisNode = (CoreNode) this;
581 writer.print(" " + thisNode.getNbChildren() + " children");
582 if (thisNode.getNbChildren() >= 1) {
583 writer.print(": [ " + thisNode.getChild(0));
584 for (int i = 1; i < thisNode.getNbChildren(); i++) {
585 writer.print(", " + thisNode.getChild(i));
586 }
587 writer.print(']');
588 }
589 writer.print('\n');
590 }
591
592 /* List of intervals in the node */
593 writer.println(" Intervals contained:");
594 for (int i = 0; i < intervals.size(); i++) {
595 writer.println(intervals.get(i).toString());
596 }
597 writer.println('\n');
598 }
599
6f4e8ec0
AM
600 // ------------------------------------------------------------------------
601 // Abstract methods
602 // ------------------------------------------------------------------------
a52fde77 603
8d47cc34
AM
604 /**
605 * Get the byte value representing the node type.
606 *
607 * @return The node type
608 */
609 public abstract byte getNodeType();
a52fde77 610
8d47cc34 611 /**
62197b87
AM
612 * Return the specific header size of this node. This means the size
613 * occupied by the type-specific section of the header (not counting the
614 * common part).
8d47cc34 615 *
62197b87 616 * @return The specific header size
8d47cc34 617 */
62197b87 618 protected abstract int getSpecificHeaderSize();
a52fde77 619
8d47cc34
AM
620 /**
621 * Read the type-specific part of the node header from a byte buffer.
622 *
623 * @param buffer
624 * The byte buffer to read from. It should be already positioned
625 * correctly.
626 */
62197b87 627 protected abstract void readSpecificHeader(ByteBuffer buffer);
a52fde77 628
8d47cc34
AM
629 /**
630 * Write the type-specific part of the header in a byte buffer.
631 *
632 * @param buffer
633 * The buffer to write to. It should already be at the correct
634 * position.
635 */
62197b87 636 protected abstract void writeSpecificHeader(ByteBuffer buffer);
a52fde77 637
8d47cc34
AM
638 /**
639 * Node-type-specific toString method. Used for debugging.
640 *
641 * @return A string representing the node
642 */
62197b87 643 protected abstract String toStringSpecific();
a52fde77 644}
This page took 0.074079 seconds and 5 git commands to generate.