| /******************************************************************************* |
| * Copyright (c) 2015 École Polytechnique de Montréal |
| * |
| * All rights reserved. This program and the accompanying materials are |
| * made available under the terms of the Eclipse Public License 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| *******************************************************************************/ |
| |
| package org.eclipse.tracecompass.statesystem.core.tests.backend.historytree; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.fail; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.channels.ClosedChannelException; |
| import java.util.List; |
| |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.HTConfig; |
| import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.HTInterval; |
| import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.HTNode; |
| import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.IHistoryTree; |
| import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue; |
| import org.eclipse.tracecompass.statesystem.core.tests.stubs.backend.HistoryTreeClassicStub; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| /** |
| * Tests the history tree |
| * |
| * @author Geneviève Bastien |
| */ |
| public class HistoryTreeTest { |
| |
| |
| /* Minimal allowed blocksize */ |
| private static final int BLOCK_SIZE = HistoryTreeClassicStub.MINIMUM_BLOCK_SIZE; |
| |
| private static final HTInterval NULL_INTERVAL = new HTInterval(10, 20, 1, TmfStateValue.nullValue()); |
| |
| /* String with 23 characters, interval in file will be 25 bytes long */ |
| private static final String TEST_STRING = "abcdefghifklmnopqrstuvw"; |
| private static final TmfStateValue STRING_VALUE = TmfStateValue.newValueString(TEST_STRING); |
| private static final HTInterval STRING_INTERVAL = new HTInterval(10, 20, 1, STRING_VALUE); |
| |
| private static final TmfStateValue LONG_VALUE = TmfStateValue.newValueLong(10L); |
| private static final HTInterval LONG_INTERVAL = new HTInterval(10, 20, 1, LONG_VALUE); |
| |
| private static final TmfStateValue INT_VALUE = TmfStateValue.newValueInt(1); |
| private static final HTInterval INT_INTERVAL = new HTInterval(10, 20, 1, INT_VALUE); |
| |
| private @Nullable File fTempFile; |
| |
| /** |
| * Create the temporary file for this history tree |
| */ |
| @Before |
| public void setupTest() { |
| try { |
| fTempFile = File.createTempFile("tmpStateSystem", null); |
| } catch (IOException e) { |
| fail(e.getMessage()); |
| } |
| } |
| |
| /** |
| * Delete the temporary history tree file after the test |
| */ |
| @After |
| public void cleanup() { |
| if (fTempFile != null) { |
| fTempFile.delete(); |
| } |
| } |
| |
| /** |
| * Setup a history tree. |
| * |
| * @param maxChildren |
| * The max number of children per node in the tree (tree config |
| * option) |
| */ |
| private HistoryTreeClassicStub setupSmallTree(int maxChildren) { |
| HistoryTreeClassicStub ht = null; |
| try { |
| File newFile = fTempFile; |
| assertNotNull(newFile); |
| HTConfig config = new HTConfig(newFile, |
| BLOCK_SIZE, |
| maxChildren, /* Number of children */ |
| 1, /* Provider version */ |
| 1); /* Start time */ |
| ht = new HistoryTreeClassicStub(config); |
| |
| } catch (IOException e) { |
| fail(e.getMessage()); |
| } |
| |
| assertNotNull(ht); |
| return ht; |
| } |
| |
| /** |
| * Setup a history tree with config MAX_CHILDREN = 3. |
| */ |
| private HistoryTreeClassicStub setupSmallTree() { |
| return setupSmallTree(3); |
| } |
| |
| /** |
| * Adds intervals a certain number of times |
| * |
| * @param ht |
| * The history tree to add the intervals to |
| * @param value |
| * The state value for every interval to have |
| * @param nbValues |
| * The number of intervals to add to the node |
| * @param start |
| * Start time of the first interval to add |
| * @return The end time of the last interval added |
| */ |
| private static long addValues(IHistoryTree ht, TmfStateValue value, int nbValues, long start) { |
| for (int i = 0; i < nbValues; i++) { |
| ht.insertInterval(new HTInterval(start + i, start + i + 1, 1, value)); |
| } |
| return start + nbValues; |
| } |
| |
| /** |
| * Fills intervals in node until it is full |
| * |
| * @param ht |
| * The history tree to add the intervals to |
| * @param value |
| * The state value for every interval to have |
| * @param node |
| * The node |
| * @param start |
| * Start time of the first interval to add |
| * @return The end time of the last interval added |
| */ |
| private static long fillValues(IHistoryTree ht, TmfStateValue value, HTNode node, long start) { |
| int i = 0; |
| while(true) { |
| HTInterval interval = new HTInterval(start + i, start + i + 1, 1, value); |
| if (interval.getSizeOnDisk(node.getNodeStart()) > node.getNodeFreeSpace()) { |
| break; |
| } |
| ht.insertInterval(interval); |
| i++; |
| } |
| return start + i; |
| |
| } |
| |
| /** |
| * Insert intervals in the tree to fill the current leaf node to capacity, |
| * without exceeding it. |
| * |
| * This guarantees that the following insertion will create new nodes. |
| * |
| * @param ht |
| * The history tree in which to insert |
| * @return Start time of the current leaf node. Future insertions should be |
| * greater than or equal to this to make sure the intervals go in |
| * the leaf node. |
| */ |
| private static long fillNextLeafNode(HistoryTreeClassicStub ht, long leafNodeStart) { |
| int prevCount = ht.getNodeCount(); |
| int prevDepth = ht.getDepth(); |
| |
| /* Fill the following leaf node */ |
| HTNode node = ht.getLatestLeaf(); |
| long ret = fillValues(ht, STRING_VALUE, node, leafNodeStart); |
| |
| /* Make sure we haven't changed the depth or node count */ |
| assertEquals(prevCount, ht.getNodeCount()); |
| assertEquals(prevDepth, ht.getDepth()); |
| |
| return ret; |
| } |
| |
| /** |
| * Test that nodes are filled |
| * |
| * It fills nodes with sequential intervals from one attribute only, so that |
| * leafs should be filled. |
| */ |
| @Test |
| public void testSequentialFill() { |
| HistoryTreeClassicStub ht = setupSmallTree(); |
| |
| HTNode node = ht.getLatestLeaf(); |
| assertEquals(0, node.getNodeUsagePercent()); |
| |
| /* Add null intervals up to ~10% */ |
| int nodeFreeSpace = node.getNodeFreeSpace(); |
| int intervalSize = NULL_INTERVAL.getSizeOnDisk(); |
| int nbIntervals = nodeFreeSpace / 10 / intervalSize; |
| long start = addValues(ht, TmfStateValue.nullValue(), nbIntervals, 1); |
| assertEquals(nodeFreeSpace - nbIntervals * intervalSize , node.getNodeFreeSpace()); |
| |
| /* Add integer intervals up to ~20% */ |
| nodeFreeSpace = node.getNodeFreeSpace(); |
| intervalSize = INT_INTERVAL.getSizeOnDisk(); |
| nbIntervals = nodeFreeSpace / 10 / intervalSize; |
| start = addValues(ht, INT_VALUE, nbIntervals, start); |
| assertEquals(nodeFreeSpace - nbIntervals * intervalSize , node.getNodeFreeSpace()); |
| |
| /* Add long intervals up to ~30% */ |
| nodeFreeSpace = node.getNodeFreeSpace(); |
| intervalSize = LONG_INTERVAL.getSizeOnDisk(); |
| nbIntervals = nodeFreeSpace / 10 / intervalSize; |
| start = addValues(ht, LONG_VALUE, nbIntervals, start); |
| assertEquals(nodeFreeSpace - nbIntervals * intervalSize , node.getNodeFreeSpace()); |
| |
| /* Add string intervals up to ~40% */ |
| nodeFreeSpace = node.getNodeFreeSpace(); |
| intervalSize = STRING_INTERVAL.getSizeOnDisk(); |
| nbIntervals = nodeFreeSpace / 10 / intervalSize; |
| start = addValues(ht, STRING_VALUE, nbIntervals, start); |
| assertEquals(nodeFreeSpace - nbIntervals * intervalSize , node.getNodeFreeSpace()); |
| |
| } |
| |
| /** |
| * Test the addition of new nodes to the tree and make sure the tree is |
| * built with the right structure |
| */ |
| @Test |
| public void testDepth() { |
| HistoryTreeClassicStub ht = setupSmallTree(); |
| |
| /* Fill a first node */ |
| HTNode node = ht.getLatestLeaf(); |
| long start = fillValues(ht, STRING_VALUE, node, 1); |
| |
| /* Add intervals that should add a sibling to the node */ |
| assertEquals(1, ht.getNodeCount()); |
| assertEquals(1, ht.getDepth()); |
| start = addValues(ht, STRING_VALUE, 1, start); |
| assertEquals(3, ht.getNodeCount()); |
| assertEquals(2, ht.getDepth()); |
| |
| /* Fill the latest leaf node (2nd child) */ |
| node = ht.getLatestLeaf(); |
| start = fillValues(ht, STRING_VALUE, node, start); |
| |
| /* |
| * Add an interval that should add another sibling to the previous nodes |
| */ |
| start = addValues(ht, STRING_VALUE, 1, start); |
| assertEquals(4, ht.getNodeCount()); |
| assertEquals(2, ht.getDepth()); |
| |
| /* Fill the latest leaf node (3rd and last child) */ |
| node = ht.getLatestLeaf(); |
| start = fillValues(ht, STRING_VALUE, node, start); |
| |
| /* The new node created here should generate a new branch */ |
| start = addValues(ht, STRING_VALUE, 1, start); |
| assertEquals(7, ht.getNodeCount()); |
| assertEquals(3, ht.getDepth()); |
| } |
| |
| /** |
| * Make sure the node sequence numbers and parent pointers are set correctly |
| * when new nodes are created. |
| * |
| * <p> |
| * We are building a tree whose node sequence numbers will look like this at |
| * the end: |
| * </p> |
| * |
| * <pre> |
| * 3 |
| * / \ |
| * 1 4 |
| * / \ \ |
| * 0 2 5 |
| * </pre> |
| * |
| * <p> |
| * However while building, the parent pointers may be different. |
| * </p> |
| * |
| * @throws ClosedChannelException |
| * If the test fails |
| */ |
| @Test |
| public void testNodeSequenceNumbers() throws ClosedChannelException { |
| /* Represents the start time of the current leaf node */ |
| long start = 1; |
| |
| HistoryTreeClassicStub ht = setupSmallTree(2); |
| start = fillNextLeafNode(ht, start); |
| |
| List<HTNode> branch = ht.getLatestBranch(); |
| assertEquals(1, branch.size()); |
| assertEquals( 0, branch.get(0).getSequenceNumber()); |
| assertEquals(-1, branch.get(0).getParentSequenceNumber()); |
| |
| /* Create a new branch */ |
| start = addValues(ht, STRING_VALUE, 1, start); |
| start = fillNextLeafNode(ht, start); |
| assertEquals(3, ht.getNodeCount()); |
| assertEquals(2, ht.getDepth()); |
| |
| /* Make sure the first node's parent was updated */ |
| HTNode node = ht.readNode(0); |
| assertEquals(0, node.getSequenceNumber()); |
| assertEquals(1, node.getParentSequenceNumber()); |
| |
| /* Make sure the new branch is alright */ |
| branch = ht.getLatestBranch(); |
| assertEquals(2, branch.size()); |
| assertEquals( 1, branch.get(0).getSequenceNumber()); |
| assertEquals(-1, branch.get(0).getParentSequenceNumber()); |
| assertEquals( 2, branch.get(1).getSequenceNumber()); |
| assertEquals( 1, branch.get(1).getParentSequenceNumber()); |
| |
| /* Create a third branch */ |
| start = addValues(ht, STRING_VALUE, 1, start); |
| start = fillNextLeafNode(ht, start); |
| assertEquals(6, ht.getNodeCount()); |
| assertEquals(3, ht.getDepth()); |
| |
| /* Make sure all previous nodes are still correct */ |
| node = ht.readNode(0); |
| assertEquals(0, node.getSequenceNumber()); |
| assertEquals(1, node.getParentSequenceNumber()); |
| node = ht.readNode(1); |
| assertEquals(1, node.getSequenceNumber()); |
| assertEquals(3, node.getParentSequenceNumber()); |
| node = ht.readNode(2); |
| assertEquals(2, node.getSequenceNumber()); |
| assertEquals(1, node.getParentSequenceNumber()); |
| |
| /* Verify the contents of the new latest branch */ |
| branch = ht.getLatestBranch(); |
| assertEquals(3, branch.size()); |
| assertEquals( 3, branch.get(0).getSequenceNumber()); |
| assertEquals(-1, branch.get(0).getParentSequenceNumber()); |
| assertEquals( 4, branch.get(1).getSequenceNumber()); |
| assertEquals( 3, branch.get(1).getParentSequenceNumber()); |
| assertEquals( 5, branch.get(2).getSequenceNumber()); |
| assertEquals( 4, branch.get(2).getParentSequenceNumber()); |
| } |
| } |