From: Alexandre Montplaisir Date: Sun, 11 Dec 2016 11:12:00 +0000 (-0500) Subject: ss: Switch the statesystem to the datastore HT X-Git-Url: http://git.efficios.com/?a=commitdiff_plain;h=cf8efcef276dab1aff04b8ddbfedb50ae8a09dab;p=deliverable%2Ftracecompass.git ss: Switch the statesystem to the datastore HT Change-Id: Ie7b57928c610172241584578494cb242905596af Signed-off-by: Alexandre Montplaisir Reviewed-on: https://git.eclipse.org/r/85163 Reviewed-by: Hudson CI Reviewed-by: Genevieve Bastien Tested-by: Genevieve Bastien --- diff --git a/statesystem/org.eclipse.tracecompass.datastore.core/META-INF/MANIFEST.MF b/statesystem/org.eclipse.tracecompass.datastore.core/META-INF/MANIFEST.MF index ac403ae469..0f5636223c 100644 --- a/statesystem/org.eclipse.tracecompass.datastore.core/META-INF/MANIFEST.MF +++ b/statesystem/org.eclipse.tracecompass.datastore.core/META-INF/MANIFEST.MF @@ -14,12 +14,12 @@ Export-Package: org.eclipse.tracecompass.internal.datastore.core;x-internal:=tru org.eclipse.tracecompass.internal.datastore.core.condition;x-internal:=true, org.eclipse.tracecompass.internal.datastore.core.historytree;x-internal:=true, org.eclipse.tracecompass.internal.datastore.core.serialization;x-internal:=true, - org.eclipse.tracecompass.internal.provisional.datastore.core.condition;x-internal:=true, + org.eclipse.tracecompass.internal.provisional.datastore.core.condition;x-friends:="org.eclipse.tracecompass.statesystem.core,org.eclipse.tracecompass.statesystem.core.tests", org.eclipse.tracecompass.internal.provisional.datastore.core.exceptions, - org.eclipse.tracecompass.internal.provisional.datastore.core.historytree;x-internal:=true, - org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.classic;x-internal:=true, + org.eclipse.tracecompass.internal.provisional.datastore.core.historytree;x-friends:="org.eclipse.tracecompass.statesystem.core,org.eclipse.tracecompass.statesystem.core.tests", + org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.classic;x-friends:="org.eclipse.tracecompass.statesystem.core,org.eclipse.tracecompass.statesystem.core.tests", org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.overlapping;x-internal:=true, - org.eclipse.tracecompass.internal.provisional.datastore.core.interval;x-internal:=true, + org.eclipse.tracecompass.internal.provisional.datastore.core.interval;x-friends:="org.eclipse.tracecompass.statesystem.core,org.eclipse.tracecompass.statesystem.core.tests", org.eclipse.tracecompass.internal.provisional.datastore.core.serialization;x-friends:="org.eclipse.tracecompass.statesystem.core,org.eclipse.tracecompass.statesystem.core.tests" Import-Package: com.google.common.annotations, com.google.common.base, diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/META-INF/MANIFEST.MF b/statesystem/org.eclipse.tracecompass.statesystem.core.tests/META-INF/MANIFEST.MF index b4ad6120a8..13a6a41f88 100644 --- a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/META-INF/MANIFEST.MF +++ b/statesystem/org.eclipse.tracecompass.statesystem.core.tests/META-INF/MANIFEST.MF @@ -20,7 +20,6 @@ Export-Package: org.eclipse.tracecompass.statesystem.core.tests, org.eclipse.tracecompass.statesystem.core.tests.perf.historytree, org.eclipse.tracecompass.statesystem.core.tests.shared.utils, org.eclipse.tracecompass.statesystem.core.tests.statevalue, - org.eclipse.tracecompass.statesystem.core.tests.stubs.backend, org.eclipse.tracecompass.statesystem.core.tests.stubs.statevalues Import-Package: com.google.common.base, com.google.common.collect, diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/HistoryTreeBackendTest.java b/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/HistoryTreeBackendTest.java index a33715ad6b..7c98eb8073 100644 --- a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/HistoryTreeBackendTest.java +++ b/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/HistoryTreeBackendTest.java @@ -20,9 +20,11 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import org.eclipse.tracecompass.internal.provisional.datastore.core.exceptions.RangeException; import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.HistoryTreeBackend; import org.eclipse.tracecompass.statesystem.core.backend.IStateHistoryBackend; import org.junit.After; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -115,4 +117,13 @@ public class HistoryTreeBackendTest extends StateHistoryBackendTestBase { fBackendMap.put(reOpenedBackend, historyTreeFile); return reOpenedBackend; } + + /** + * This backend throws a different exception. + */ + @Override + @Test(expected = RangeException.class) + public void testIntervalBeforeStart() { + super.testIntervalBeforeStart(); + } } diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/historytree/HTIntervalStringReadWriteTest.java b/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/historytree/HTIntervalStringReadWriteTest.java deleted file mode 100644 index 1f1b7c8f18..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/historytree/HTIntervalStringReadWriteTest.java +++ /dev/null @@ -1,184 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - *******************************************************************************/ - -package org.eclipse.tracecompass.statesystem.core.tests.backend.historytree; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.FileChannel; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.HTInterval; -import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; - -/** - * Test the reading/writing logic in {@link HTInterval}, particularly regarding - * the string length limitations. - * - * @author Alexandre Montplaisir - */ -@RunWith(Parameterized.class) -@NonNullByDefault({}) -public class HTIntervalStringReadWriteTest { - - private static final Charset CHARSET = Charset.forName("UTF-8"); - - private int fNbChars; - private int fCharLength; - private char fChar; - - /** - * Parameter generator. - * - * Generates a combination of all possible string lengths and all possible - * character lengths. - * - * @return The test parameters - */ - @Parameters(name = "nb of chars: {0}, char length: {1}") - public static Iterable getTestParams() { - Set> set = Sets.cartesianProduct(ImmutableList.of( - ImmutableSet.of( - 0, - 10, - Byte.MAX_VALUE - 1, - (int) Byte.MAX_VALUE, - Byte.MAX_VALUE + 1, - - Short.MAX_VALUE / 2 - 1, - Short.MAX_VALUE / 2, - Short.MAX_VALUE / 2 + 1, - - Short.MAX_VALUE / 3 - 1, - Short.MAX_VALUE / 3, - Short.MAX_VALUE / 3 + 1, - - Short.MAX_VALUE - 1, - (int) Short.MAX_VALUE, - Short.MAX_VALUE + 1), - - ImmutableSet.of(1, 2, 3) - )); - - return set.stream() - .map(List::toArray) - .collect(Collectors.toList()); - } - - /** - * Test constructor, take the generated parameters as parameters. - * - * @param nbChars - * The number of characters in the test string. - * @param charLength - * The length (in bytes) of the UTF-8-encoded form of the - * character being used. - */ - public HTIntervalStringReadWriteTest(Integer nbChars, Integer charLength) { - fNbChars = nbChars.intValue(); - fCharLength = charLength.intValue(); - switch (charLength) { - case 1: - fChar = 'a'; - break; - case 2: - fChar = 'é'; - break; - case 3: - fChar = '长'; // "chang" means long / length in Chinese! - break; - default: - throw new IllegalArgumentException(); - } - } - - /** - * Test method - * - * @throws IOException - * Fails the test - */ - @Test - public void testStringWithChars() throws IOException { - StringBuilder sb = new StringBuilder(); - IntStream.range(0, fNbChars).forEach(i -> sb.append(fChar)); - String str = sb.toString(); - assertEquals(fNbChars, str.length()); - assertEquals(fNbChars * fCharLength, str.getBytes(CHARSET).length); - - TmfStateValue value = TmfStateValue.newValueString(str); - if (fNbChars * fCharLength > Short.MAX_VALUE) { - /* For sizes that are known to be too long, expect an exception */ - try { - new HTInterval(0, 10, 1, value); - } catch (IllegalArgumentException e) { - return; - } - fail(); - } - HTInterval interval = new HTInterval(0, 10, 1, value); - writeAndReadInterval(interval); - } - - private static void writeAndReadInterval(HTInterval interval) throws IOException { - int sizeOnDisk = interval.getSizeOnDisk(); - - /* Write the interval to a file */ - File tempFile = File.createTempFile("test-interval", ".interval"); - try (FileOutputStream fos = new FileOutputStream(tempFile, false); - FileChannel fc = fos.getChannel();) { - - ByteBuffer bb = ByteBuffer.allocate(sizeOnDisk); - bb.order(ByteOrder.LITTLE_ENDIAN); - bb.clear(); - - interval.writeInterval(bb); - bb.flip(); - int written = fc.write(bb); - assertEquals(sizeOnDisk, written); - } - - /* Read the interval from the file */ - HTInterval readInterval; - try (FileInputStream fis = new FileInputStream(tempFile); - FileChannel fc = fis.getChannel();) { - - ByteBuffer bb = ByteBuffer.allocate(sizeOnDisk); - bb.order(ByteOrder.LITTLE_ENDIAN); - bb.clear(); - - int read = fc.read(bb); - assertEquals(sizeOnDisk, read); - bb.flip(); - readInterval = HTInterval.readFrom(bb); - } - - assertEquals(interval, readInterval); - } -} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/historytree/HistoryTreeTest.java b/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/historytree/HistoryTreeTest.java deleted file mode 100644 index 13b759c353..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/historytree/HistoryTreeTest.java +++ /dev/null @@ -1,323 +0,0 @@ -/******************************************************************************* - * 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 v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - *******************************************************************************/ - -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); - } - - private static long fillValues(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; - } - - /** - * 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(); - int intervalSize = STRING_INTERVAL.getSizeOnDisk(); - int nodeFreeSpace = node.getNodeFreeSpace(); - int nbIntervals = nodeFreeSpace / intervalSize; - long ret = fillValues(ht, STRING_VALUE, nbIntervals, 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 = fillValues(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 = fillValues(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 = fillValues(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 = fillValues(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(); - int nodeFreeSpace = node.getNodeFreeSpace(); - int nbIntervals = nodeFreeSpace / (STRING_INTERVAL.getSizeOnDisk()); - long start = fillValues(ht, STRING_VALUE, nbIntervals, 1); - - /* Add intervals that should add a sibling to the node */ - assertEquals(1, ht.getNodeCount()); - assertEquals(1, ht.getDepth()); - start = fillValues(ht, STRING_VALUE, 1, start); - assertEquals(3, ht.getNodeCount()); - assertEquals(2, ht.getDepth()); - - /* Fill the latest leaf node (2nd child) */ - node = ht.getLatestLeaf(); - nodeFreeSpace = node.getNodeFreeSpace(); - nbIntervals = nodeFreeSpace / (STRING_INTERVAL.getSizeOnDisk()); - start = fillValues(ht, STRING_VALUE, nbIntervals, start); - - /* - * Add an interval that should add another sibling to the previous nodes - */ - start = fillValues(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(); - nodeFreeSpace = node.getNodeFreeSpace(); - nbIntervals = nodeFreeSpace / (STRING_INTERVAL.getSizeOnDisk()); - start = fillValues(ht, STRING_VALUE, nbIntervals, start); - - /* The new node created here should generate a new branch */ - start = fillValues(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. - * - *

- * We are building a tree whose node sequence numbers will look like this at - * the end: - *

- * - *
-     *     3
-     *    / \
-     *   1   4
-     *  / \   \
-     * 0   2   5
-     * 
- * - *

- * However while building, the parent pointers may be different. - *

- * - * @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 branch = ht.getLatestBranch(); - assertEquals(1, branch.size()); - assertEquals( 0, branch.get(0).getSequenceNumber()); - assertEquals(-1, branch.get(0).getParentSequenceNumber()); - - /* Create a new branch */ - start = fillValues(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 = fillValues(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()); - } -} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/historytree/HistoryTreeWithBackendTest.java b/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/historytree/HistoryTreeWithBackendTest.java deleted file mode 100644 index 85ca8dcf7d..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/historytree/HistoryTreeWithBackendTest.java +++ /dev/null @@ -1,134 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 É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 v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - *******************************************************************************/ - -package org.eclipse.tracecompass.statesystem.core.tests.backend.historytree; - -import static org.junit.Assert.fail; - -import java.io.File; -import java.io.IOException; -import java.util.Arrays; - -import org.eclipse.tracecompass.common.core.NonNullUtils; -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.HistoryTreeBackend; -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.HistoryTreeBackendStub; -import org.eclipse.tracecompass.statesystem.core.tests.stubs.backend.HistoryTreeBackendStub.HistoryTreeType; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -/** - * Test the {@link HistoryTreeBackend}-specific behavior and its interactions - * with the {@link IHistoryTree} classes. - * - * @author Geneviève Bastien - */ -@RunWith(Parameterized.class) -public class HistoryTreeWithBackendTest { - - /** State system ID */ - private static final String SSID = "test"; - /** Provider version */ - private static final int PROVIDER_VERSION = 0; - - /** Default maximum number of children nodes */ - private static final int MAX_CHILDREN = 3; - /** Default block size */ - private static final int BLOCK_SIZE = 4096; - - private final HistoryTreeType fHtType; - - /** - * @return The arrays of parameters - */ - @Parameters(name = "{0}") - public static Iterable getParameters() { - return Arrays.asList(new Object[][] { - { HistoryTreeType.CLASSIC } - }); - } - - /** - * Constructor - * - * @param htType - * The type of history tree to use - */ - public HistoryTreeWithBackendTest(HistoryTreeType htType) { - fHtType = htType; - } - - /** - * Test the behavior of the history tree after at least a depth of 3 - */ - @Test - public void testFillNodes() { - try { - // Test case parameters - final int nbAttr = 5; - final int depthToRead = 3; - - long startTime = 1; - File historyTreeFile = NonNullUtils.checkNotNull(File.createTempFile("HistoryTreeBackendTest", ".ht")); - HistoryTreeBackendStub.setTreeType(fHtType); - HistoryTreeBackendStub backend = new HistoryTreeBackendStub(SSID, historyTreeFile, PROVIDER_VERSION, startTime, BLOCK_SIZE, MAX_CHILDREN); - - int duration = nbAttr; - int quarkTest = nbAttr; - long time = startTime + duration; - - HTInterval interval = new HTInterval(startTime, time, quarkTest, TmfStateValue.newValueLong(time)); - // Insert a first interval for the test attribute - backend.insertPastState(interval.getStartTime(), interval.getEndTime(), interval.getAttribute(), interval.getStateValue()); - - /* - * insert cascading intervals to fill 2 levels of history tree, so - * that we start another branch - */ - while (backend.getHistoryTree().getDepth() < depthToRead) { - backend.insertPastState( - Math.max(startTime, time - duration), - time - 1, - (int) time % nbAttr, - TmfStateValue.newValueLong(time)); - time++; - } - - // entirely fill the latest leaf with cascading intervals - HTNode latestLeaf = backend.getHistoryTree().getLatestLeaf(); - /* - * Add an interval while there is still room for it or make sure the - * node does not get written on disk in the meantime. - */ - while (interval.getSizeOnDisk() <= latestLeaf.getNodeFreeSpace() || latestLeaf.isOnDisk()) { - backend.insertPastState( - Math.max(startTime, time - duration), - time - 1, - (int) time % nbAttr, - TmfStateValue.newValueLong(time)); - time++; - } - - // Add an interval that does not fit in latest leaf, but starts - // before the current branch - backend.insertPastState(interval.getEndTime() + 1, time, quarkTest, TmfStateValue.newValueLong(time)); - - backend.getHistoryTree().assertIntegrity(); - - } catch (IOException e) { - fail(e.getMessage()); - } - } - -} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/historytree/SSIntervalStringReadWriteTest.java b/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/historytree/SSIntervalStringReadWriteTest.java new file mode 100644 index 0000000000..5ceac606f9 --- /dev/null +++ b/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/backend/historytree/SSIntervalStringReadWriteTest.java @@ -0,0 +1,191 @@ +/******************************************************************************* + * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.tracecompass.statesystem.core.tests.backend.historytree; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.tracecompass.internal.provisional.datastore.core.serialization.ISafeByteBufferReader; +import org.eclipse.tracecompass.internal.provisional.datastore.core.serialization.ISafeByteBufferWriter; +import org.eclipse.tracecompass.internal.provisional.datastore.core.serialization.SafeByteBufferFactory; +import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.StateSystemInterval; +import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; + +/** + * Test the reading/writing logic in {@link StateSystemInterval}, particularly regarding + * the string length limitations. + * + * @author Alexandre Montplaisir + */ +@RunWith(Parameterized.class) +@NonNullByDefault({}) +public class SSIntervalStringReadWriteTest { + + private static final Charset CHARSET = Charset.forName("UTF-8"); + + private int fNbChars; + private int fCharLength; + private char fChar; + + /** + * Parameter generator. + * + * Generates a combination of all possible string lengths and all possible + * character lengths. + * + * @return The test parameters + */ + @Parameters(name = "nb of chars: {0}, char length: {1}") + public static Iterable getTestParams() { + Set> set = Sets.cartesianProduct(ImmutableList.of( + ImmutableSet.of( + 0, + 10, + Byte.MAX_VALUE - 1, + (int) Byte.MAX_VALUE, + Byte.MAX_VALUE + 1, + + Short.MAX_VALUE / 2 - 1, + Short.MAX_VALUE / 2, + Short.MAX_VALUE / 2 + 1, + + Short.MAX_VALUE / 3 - 1, + Short.MAX_VALUE / 3, + Short.MAX_VALUE / 3 + 1, + + Short.MAX_VALUE - 1, + (int) Short.MAX_VALUE, + Short.MAX_VALUE + 1), + + ImmutableSet.of(1, 2, 3) + )); + + return set.stream() + .map(List::toArray) + .collect(Collectors.toList()); + } + + /** + * Test constructor, take the generated parameters as parameters. + * + * @param nbChars + * The number of characters in the test string. + * @param charLength + * The length (in bytes) of the UTF-8-encoded form of the + * character being used. + */ + public SSIntervalStringReadWriteTest(Integer nbChars, Integer charLength) { + fNbChars = nbChars.intValue(); + fCharLength = charLength.intValue(); + switch (charLength) { + case 1: + fChar = 'a'; + break; + case 2: + fChar = 'é'; + break; + case 3: + fChar = '长'; // "chang" means long / length in Chinese! + break; + default: + throw new IllegalArgumentException(); + } + } + + /** + * Test method + * + * @throws IOException + * Fails the test + */ + @Test + public void testStringWithChars() throws IOException { + StringBuilder sb = new StringBuilder(); + IntStream.range(0, fNbChars).forEach(i -> sb.append(fChar)); + String str = sb.toString(); + assertEquals(fNbChars, str.length()); + assertEquals(fNbChars * fCharLength, str.getBytes(CHARSET).length); + + TmfStateValue value = TmfStateValue.newValueString(str); + if (fNbChars * fCharLength > Short.MAX_VALUE) { + /* For sizes that are known to be too long, expect an exception */ + try { + new StateSystemInterval(0, 10, 1, value); + } catch (IllegalArgumentException e) { + return; + } + fail(); + } + StateSystemInterval interval = new StateSystemInterval(0, 10, 1, value); + writeAndReadInterval(interval); + } + + private static void writeAndReadInterval(StateSystemInterval interval) throws IOException { + int sizeOnDisk = interval.getSizeOnDisk(); + + /* Write the interval to a file */ + File tempFile = File.createTempFile("test-interval", ".interval"); + try (FileOutputStream fos = new FileOutputStream(tempFile, false); + FileChannel fc = fos.getChannel();) { + + ByteBuffer bb = ByteBuffer.allocate(sizeOnDisk); + bb.order(ByteOrder.LITTLE_ENDIAN); + bb.clear(); + + ISafeByteBufferWriter sbbw = SafeByteBufferFactory.wrapWriter(bb, sizeOnDisk); + interval.writeSegment(sbbw); + + bb.flip(); + int written = fc.write(bb); + assertEquals(sizeOnDisk, written); + } + + /* Read the interval from the file */ + StateSystemInterval readInterval; + try (FileInputStream fis = new FileInputStream(tempFile); + FileChannel fc = fis.getChannel();) { + + ByteBuffer bb = ByteBuffer.allocate(sizeOnDisk); + bb.order(ByteOrder.LITTLE_ENDIAN); + bb.clear(); + + int read = fc.read(bb); + assertEquals(sizeOnDisk, read); + bb.flip(); + + ISafeByteBufferReader sbbr = SafeByteBufferFactory.wrapReader(bb, sizeOnDisk); + readInterval = StateSystemInterval.DESERIALISER.readInterval(sbbr); + } + + assertEquals(interval, readInterval); + } +} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/stubs/org/eclipse/tracecompass/statesystem/core/tests/stubs/backend/HistoryTreeBackendStub.java b/statesystem/org.eclipse.tracecompass.statesystem.core.tests/stubs/org/eclipse/tracecompass/statesystem/core/tests/stubs/backend/HistoryTreeBackendStub.java deleted file mode 100644 index ede7014b0e..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/stubs/org/eclipse/tracecompass/statesystem/core/tests/stubs/backend/HistoryTreeBackendStub.java +++ /dev/null @@ -1,170 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 É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 v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - *******************************************************************************/ - -package org.eclipse.tracecompass.statesystem.core.tests.stubs.backend; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; - -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.HTConfig; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.HistoryTreeBackend; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.IHistoryTree; - -/** - * Stub class for the {@link HistoryTreeBackend}. It creates a - * {@link HistoryTreeClassicStub} to grant access to some protected methods. - * - * @author Geneviève Bastien - */ -public class HistoryTreeBackendStub extends HistoryTreeBackend { - - private static HistoryTreeType HT_TYPE = HistoryTreeType.CLASSIC; - - /** - * Sets the type of tree to build. Since the history tree is initialized in - * the parent's constructor, this stub class needs to know the type of tree - * to build. - * - * @param htType - * The type of history tree to build for this backend - */ - public static void setTreeType(HistoryTreeType htType) { - HT_TYPE = htType; - } - - /** - * Enumeration of all history tree types implemented. This will be used to - * create the right type of history tree - */ - public enum HistoryTreeType { - /** - * The classic history tree - */ - CLASSIC - } - - /** - * Constructor for new history files. Use this when creating a new history - * from scratch. - * - * @param ssid - * The state system's ID - * @param newStateFile - * The filename/location where to store the state history (Should - * end in .ht) - * @param providerVersion - * Version of of the state provider. We will only try to reopen - * existing files if this version matches the one in the - * framework. - * @param startTime - * The earliest time stamp that will be stored in the history - * @param blockSize - * The size of the blocks in the history file. This should be a - * multiple of 4096. - * @param maxChildren - * The maximum number of children each core node can have - * @throws IOException - * Thrown if we can't create the file for some reason - */ - public HistoryTreeBackendStub(String ssid, - File newStateFile, - int providerVersion, - long startTime, - int blockSize, - int maxChildren) throws IOException { - super(ssid, newStateFile, providerVersion, startTime, blockSize, maxChildren); - } - - /** - * Existing history constructor. Use this to open an existing state-file. - * - * @param ssid - * The state system's id - * @param existingStateFile - * Filename/location of the history we want to load - * @param providerVersion - * Expected version of of the state provider plugin. - * @throws IOException - * If we can't read the file, if it doesn't exist, is not - * recognized, or if the version of the file does not match the - * expected providerVersion. - */ - public HistoryTreeBackendStub(String ssid, File existingStateFile, int providerVersion) - throws IOException { - super(ssid, existingStateFile, providerVersion); - } - - @Override - protected IHistoryTree initializeSHT(HTConfig conf) throws IOException { - switch (HT_TYPE) { - case CLASSIC: - return new HistoryTreeClassicStub(conf); - default: - return new HistoryTreeClassicStub(conf); - } - } - - @Override - protected IHistoryTree initializeSHT(File existingStateFile, int providerVersion) throws IOException { - switch (HT_TYPE) { - case CLASSIC: - return new HistoryTreeClassicStub(existingStateFile, providerVersion); - default: - return new HistoryTreeClassicStub(existingStateFile, providerVersion); - } - } - - /** - * Get the History Tree built by this backend. - * - * @return The history tree - */ - public HistoryTreeClassicStub getHistoryTree() { - return (HistoryTreeClassicStub) super.getSHT(); - } - - /** - * Debug method to print the contents of the history backend. - * - * @param writer - * The PrintWriter where to write the output - */ - public void debugPrint(PrintWriter writer) { - /* By default don't print out all the intervals */ - debugPrint(writer, false, -1); - } - - /** - * The basic debugPrint method will print the tree structure, but not their - * contents. - * - * This method here print the contents (the intervals) as well. - * - * @param writer - * The PrintWriter to which the debug info will be written - * @param printIntervals - * Should we also print every contained interval individually? - * @param ts - * The timestamp that nodes have to intersect for intervals to be - * printed. A negative value will print intervals for all nodes. - * The timestamp only applies if printIntervals is true. - */ - public void debugPrint(PrintWriter writer, boolean printIntervals, long ts) { - /* Only used for debugging, shouldn't be externalized */ - writer.println("------------------------------"); //$NON-NLS-1$ - writer.println("State History Tree:\n"); //$NON-NLS-1$ - writer.println(getSHT().toString()); - writer.println("Average node utilization: " //$NON-NLS-1$ - + getAverageNodeUsage()); - writer.println(""); //$NON-NLS-1$ - - getHistoryTree().debugPrintFullTree(writer, printIntervals, ts); - } -} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/stubs/org/eclipse/tracecompass/statesystem/core/tests/stubs/backend/HistoryTreeClassicStub.java b/statesystem/org.eclipse.tracecompass.statesystem.core.tests/stubs/org/eclipse/tracecompass/statesystem/core/tests/stubs/backend/HistoryTreeClassicStub.java deleted file mode 100644 index 2508db5374..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/stubs/org/eclipse/tracecompass/statesystem/core/tests/stubs/backend/HistoryTreeClassicStub.java +++ /dev/null @@ -1,280 +0,0 @@ -/******************************************************************************* - * 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 v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - *******************************************************************************/ - -package org.eclipse.tracecompass.statesystem.core.tests.stubs.backend; - -import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.channels.ClosedChannelException; -import java.util.Collection; -import java.util.List; - -import org.eclipse.tracecompass.internal.statesystem.core.Activator; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.HTConfig; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.HTNode; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.IHistoryTree; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.ParentNode; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.classic.CoreNode; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.classic.HistoryTreeClassic; - -import com.google.common.collect.Iterables; - -/** - * Stub class to unit test the history tree. You can set the size of the - * interval section before using the tree, in order to fine-tune the test. - * - * Note to developers: This tree is not meant to be used with a backend. It just - * exposes some info from the history tree. - * - * @author Geneviève Bastien - */ -public class HistoryTreeClassicStub extends HistoryTreeClassic { - - /** - * Minimum size a block of this tree should have - */ - public static final int MINIMUM_BLOCK_SIZE = IHistoryTree.TREE_HEADER_SIZE; - - /** - * Constructor for this history tree stub - * - * @param conf - * The config to use for this History Tree. - * @throws IOException - * If an error happens trying to open/write to the file - * specified in the config - */ - public HistoryTreeClassicStub(HTConfig conf) throws IOException { - super(conf); - } - - /** - * "Reader" constructor : instantiate a SHTree from an existing tree file on - * disk - * - * @param existingStateFile - * Path/filename of the history-file we are to open - * @param expProviderVersion - * The expected version of the state provider - * @throws IOException - * If an error happens reading the file - */ - public HistoryTreeClassicStub(File existingStateFile, int expProviderVersion) throws IOException { - super(existingStateFile, expProviderVersion); - } - - // ------------------------------------------------------------------------ - // Extra test accessors - // ------------------------------------------------------------------------ - - @Override - public List getLatestBranch() { - /* Super method is not public */ - return checkNotNull(super.getLatestBranch()); - } - - /** - * Get the latest leaf of the tree - * - * @return The current leaf node of the tree - */ - public HTNode getLatestLeaf() { - List latest = getLatestBranch(); - return Iterables.getLast(latest); - } - - /** - * Get the node from the latest branch at a given position, 0 being the root - * and being a leaf node. - * - * @param pos - * The position at which to return the node - * @return The node at position pos - */ - public HTNode getNodeAt(int pos) { - List latest = getLatestBranch(); - return latest.get(pos); - } - - /** - * Get the depth of the tree - * - * @return The depth of the tree - */ - public int getDepth() { - return getLatestBranch().size(); - } - - // ------------------------------------------------------------------------ - // Debug printing methods - // ------------------------------------------------------------------------ - - /** - * Print out the full tree for debugging purposes - * - * @param writer - * PrintWriter in which to write the output - * @param printIntervals - * Flag to enable full output of the interval information - * @param ts - * The timestamp that nodes have to intersect for intervals to be - * printed. A negative value will print intervals for all nodes. - * The timestamp only applies if printIntervals is true. - */ - public void debugPrintFullTree(PrintWriter writer, boolean printIntervals, long ts) { - preOrderPrint(writer, false, getLatestBranch().get(0), 0, ts); - - if (printIntervals) { - preOrderPrint(writer, true, getLatestBranch().get(0), 0, ts); - } - writer.println('\n'); - } - - /** - * Start at currentNode and print the contents of all its children, in - * pre-order. Give the root node in parameter to visit the whole tree, and - * have a nice overview. - */ - private void preOrderPrint(PrintWriter writer, boolean printIntervals, - HTNode currentNode, int curDepth, long ts) { - - writer.println(currentNode.toString()); - /* - * Print intervals only if timestamp is negative or within the range of - * the node - */ - if (printIntervals && - (ts <= 0 || - (ts >= currentNode.getNodeStart() && ts <= currentNode.getNodeEnd()))) { - currentNode.debugPrintIntervals(writer); - } - - switch (currentNode.getNodeType()) { - case LEAF: - /* Stop if it's the leaf node */ - return; - - case CORE: - try { - final CoreNode node = (CoreNode) currentNode; - /* If node extensions were used, they would be printed here. */ - - /* Print the child nodes */ - for (int i = 0; i < node.getNbChildren(); i++) { - HTNode nextNode = getTreeIO().readNode(node.getChild(i)); - for (int j = 0; j < curDepth; j++) { - writer.print(" "); - } - writer.print("+-"); - preOrderPrint(writer, printIntervals, nextNode, curDepth + 1, ts); - } - } catch (ClosedChannelException e) { - Activator.getDefault().logError(e.getMessage()); - } - break; - - default: - break; - } - } - - // ------------------------------------------------------------------------ - // Assertion methods, for use with JUnit tests - // ------------------------------------------------------------------------ - - /** - * Check the integrity of all the nodes in the tree. Calls - * {@link #assertNodeIntegrity} for every node in the tree. - */ - public void assertIntegrity() { - try { - for (int i = 0; i < getNodeCount(); i++) { - assertNodeIntegrity(getNode(i)); - } - } catch (ClosedChannelException e) { - fail(e.getMessage()); - } - } - - /** - * Debugging method to make sure all intervals contained in the given node - * have valid start and end times. - * - * @param node - * The node to check - */ - private void assertNodeIntegrity(HTNode node) { - if (node instanceof ParentNode) { - assertChildrenIntegrity((ParentNode) node); - } - - /* Check that all intervals are within the node's range */ - // TODO: Get the intervals of a node - - } - - private void assertChildrenIntegrity(ParentNode node) { - try { - /* - * Test that this node's start and end times match the start of the - * first child and the end of the last child, respectively - */ - if (node.getNbChildren() > 0) { - HTNode childNode = getNode(node.getChild(0)); - assertEquals("Equals start time of parent " + node.getSequenceNumber() + " and first child " + childNode.getSequenceNumber(), - node.getNodeStart(), childNode.getNodeStart()); - if (node.isOnDisk()) { - childNode = getNode(node.getLatestChild()); - assertEquals("Equals end time of parent " + node.getSequenceNumber() + " and last child " + childNode.getSequenceNumber(), - node.getNodeEnd(), childNode.getNodeEnd()); - } - } - - /* - * Test that the childStartTimes[] array matches the real nodes' - * start times - * - * Also test that children range is within the parent's range - */ - for (int i = 0; i < node.getNbChildren(); i++) { - HTNode childNode = getNode(node.getChild(i)); - assertEquals("Start time in parent " + node.getSequenceNumber() + " of child at index " + i, - childNode.getNodeStart(), node.getChildStart(i)); - assertTrue("Child at index " + i + " of parent " + node.getSequenceNumber() + " has correct start time", - node.getNodeStart() <= childNode.getNodeStart()); - if (node.isOnDisk() && childNode.isOnDisk()) { - assertTrue("Child at index " + i + " of parent " + node.getSequenceNumber() + " has correct start time", - childNode.getNodeEnd() <= childNode.getNodeEnd()); - } - testIntersectingChildren(node, childNode); - } - - } catch (ClosedChannelException e) { - fail(e.getMessage()); - } - } - - private static void testIntersectingChildren(ParentNode parent, HTNode child) { - int childSequence = child.getSequenceNumber(); - boolean shouldBeInCollection; - Collection nextChildren; - for (long t = parent.getNodeStart(); t < parent.getNodeEnd(); t++) { - shouldBeInCollection = (t >= child.getNodeStart() && t <= child.getNodeEnd()); - nextChildren = parent.selectNextChildren(t); - assertEquals(shouldBeInCollection, nextChildren.contains(childSequence)); - } - } - -} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/stubs/org/eclipse/tracecompass/statesystem/core/tests/stubs/backend/package-info.java b/statesystem/org.eclipse.tracecompass.statesystem.core.tests/stubs/org/eclipse/tracecompass/statesystem/core/tests/stubs/backend/package-info.java deleted file mode 100644 index 6d73c86605..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/stubs/org/eclipse/tracecompass/statesystem/core/tests/stubs/backend/package-info.java +++ /dev/null @@ -1,11 +0,0 @@ -/******************************************************************************* - * 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 v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - *******************************************************************************/ - -@org.eclipse.jdt.annotation.NonNullByDefault -package org.eclipse.tracecompass.statesystem.core.tests.stubs.backend; diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/META-INF/MANIFEST.MF b/statesystem/org.eclipse.tracecompass.statesystem.core/META-INF/MANIFEST.MF index 81f3d3cb3f..444f4fc941 100644 --- a/statesystem/org.eclipse.tracecompass.statesystem.core/META-INF/MANIFEST.MF +++ b/statesystem/org.eclipse.tracecompass.statesystem.core/META-INF/MANIFEST.MF @@ -16,7 +16,6 @@ Export-Package: org.eclipse.tracecompass.internal.provisional.statesystem.core.s org.eclipse.tracecompass.internal.statesystem.core;x-friends:="org.eclipse.tracecompass.statesystem.core.tests", org.eclipse.tracecompass.internal.statesystem.core.backend;x-internal:=true, org.eclipse.tracecompass.internal.statesystem.core.backend.historytree;x-friends:="org.eclipse.tracecompass.statesystem.core.tests", - org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.classic;x-friends:="org.eclipse.tracecompass.statesystem.core.tests", org.eclipse.tracecompass.statesystem.core, org.eclipse.tracecompass.statesystem.core.backend, org.eclipse.tracecompass.statesystem.core.exceptions, diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HTConfig.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HTConfig.java deleted file mode 100644 index 597df81238..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HTConfig.java +++ /dev/null @@ -1,125 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012, 2014 Ericsson - * Copyright (c) 2010, 2011 École Polytechnique de Montréal - * Copyright (c) 2010, 2011 Alexandre Montplaisir - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - *******************************************************************************/ - -package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; - -import java.io.File; - -/** - * Configuration object for the {@link IHistoryTree}. - * - * @author Alexandre Montplaisir - */ -public final class HTConfig { - - private static final int DEFAULT_BLOCKSIZE = 64 * 1024; - private static final int DEFAULT_MAXCHILDREN = 50; - - private final File stateFile; - private final int blockSize; - private final int maxChildren; - private final int providerVersion; - private final long treeStart; - - /** - * Full constructor. - * - * @param newStateFile - * The name of the history file - * @param blockSize - * The size of each "block" on disk. One node will always fit in - * one block. - * @param maxChildren - * The maximum number of children allowed per core (non-leaf) - * node. - * @param providerVersion - * The version of the state provider. If a file already exists, - * and their versions match, the history file will not be rebuilt - * uselessly. - * @param startTime - * The start time of the history - */ - public HTConfig(File newStateFile, int blockSize, int maxChildren, - int providerVersion, long startTime) { - this.stateFile = newStateFile; - this.blockSize = blockSize; - this.maxChildren = maxChildren; - this.providerVersion = providerVersion; - this.treeStart = startTime; - } - - /** - * Version of the constructor using default values for 'blockSize' and - * 'maxChildren'. - * - * @param newStateFile - * The name of the history file - * @param providerVersion - * The version of the state provider. If a file already exists, - * and their versions match, the history file will not be rebuilt - * uselessly. - * @param startTime - * The start time of the history - */ - public HTConfig(File newStateFile, int providerVersion, long startTime) { - this(newStateFile, DEFAULT_BLOCKSIZE, DEFAULT_MAXCHILDREN, providerVersion, startTime); - } - - // ------------------------------------------------------------------------ - // Getters - // ------------------------------------------------------------------------ - - /** - * Get the history file. - * - * @return The history file - */ - public File getStateFile() { - return stateFile; - } - - /** - * Get the configure block size. - * - * @return The block size - */ - public int getBlockSize() { - return blockSize; - } - - /** - * Get the maximum amount of children allowed. - * - * @return The maximum amount of children - */ - public int getMaxChildren() { - return maxChildren; - } - - /** - * Get the state provider's version. - * - * @return The state provider's version - */ - public int getProviderVersion() { - return providerVersion; - } - - /** - * Get the start time of the history - * - * @return The start time - */ - public long getTreeStart() { - return treeStart; - } -} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HTInterval.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HTInterval.java deleted file mode 100644 index e0ab848711..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HTInterval.java +++ /dev/null @@ -1,411 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012, 2015 Ericsson, École Polytechnique de Montréal - * Copyright (c) 2010, 2011 Alexandre Montplaisir - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Alexandre Montplaisir - Initial API and implementation - * Florian Wininger - Allow to change the size of a interval - * Patrick Tasse - Add message to exceptions - *******************************************************************************/ - -package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.tracecompass.internal.provisional.datastore.core.serialization.ISafeByteBufferReader; -import org.eclipse.tracecompass.internal.provisional.datastore.core.serialization.ISafeByteBufferWriter; -import org.eclipse.tracecompass.internal.provisional.datastore.core.serialization.SafeByteBufferFactory; -import org.eclipse.tracecompass.internal.provisional.statesystem.core.statevalue.CustomStateValue; -import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; -import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; -import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue; -import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue; - -/** - * The interval component, which will be contained in a node of the History - * Tree. - * - * @author Alexandre Montplaisir - */ -public final class HTInterval implements ITmfStateInterval { - - private static final Charset CHARSET = Charset.forName("UTF-8"); //$NON-NLS-1$ - - private static final String errMsg = "Invalid interval data. Maybe your file is corrupt?"; //$NON-NLS-1$ - - /* 'Byte' equivalent for state values types */ - private static final byte TYPE_NULL = -1; - private static final byte TYPE_INTEGER = 0; - private static final byte TYPE_STRING = 1; - private static final byte TYPE_LONG = 2; - private static final byte TYPE_DOUBLE = 3; - private static final byte TYPE_CUSTOM = 20; - - private final long start; - private final long end; - private final int attribute; - private final @NonNull TmfStateValue sv; - - /** Number of bytes used by this interval when it is written to disk */ - private final int fSizeOnDisk; - - /** - * Standard constructor - * - * @param intervalStart - * Start time of the interval - * @param intervalEnd - * End time of the interval - * @param attribute - * Attribute (quark) to which the state represented by this - * interval belongs - * @param value - * State value represented by this interval - * @throws TimeRangeException - * If the start time or end time are invalid - */ - public HTInterval(long intervalStart, long intervalEnd, int attribute, - @NonNull TmfStateValue value) throws TimeRangeException { - if (intervalStart > intervalEnd) { - throw new TimeRangeException("Start:" + intervalStart + ", End:" + intervalEnd); //$NON-NLS-1$ //$NON-NLS-2$ - } - - this.start = intervalStart; - this.end = intervalEnd; - this.attribute = attribute; - this.sv = value; - this.fSizeOnDisk = computeSizeOnDisk(sv); - } - - /** - * Compute how much space (in bytes) an interval will take in its serialized - * form on disk. This is dependent on its state value. - */ - private static int computeSizeOnDisk(ITmfStateValue sv) { - /* - * Minimum size is 2x long (start and end), 1x int (attribute) and 1x - * byte (value type). - */ - int minSize = Long.BYTES + Long.BYTES + Integer.BYTES + Byte.BYTES; - - switch (sv.getType()) { - case NULL: - return minSize; - case INTEGER: - return (minSize + Integer.BYTES); - case LONG: - return (minSize + Long.BYTES); - case DOUBLE: - return (minSize + Double.BYTES); - case STRING: - String str = sv.unboxStr(); - int strLength = str.getBytes(CHARSET).length; - - if (strLength > Short.MAX_VALUE) { - throw new IllegalArgumentException("String is too long to be stored in state system: " + str); //$NON-NLS-1$ - } - - /* - * String's length + 3 (2 bytes for size, 1 byte for \0 at the end) - */ - return (minSize + strLength + 3); - case CUSTOM: - /* Length of serialized value (short) + state value */ - return (minSize + Short.BYTES + ((CustomStateValue) sv).getSerializedSize()); - default: - /* - * It's very important that we know how to write the state value in - * the file!! - */ - throw new IllegalStateException(); - } - } - - /** - * "Faster" constructor for inner use only. When we build an interval when - * reading it from disk (with {@link #readFrom}), we already know the size - * of the strings entry, so there is no need to call - * {@link #computeStringsEntrySize()} and do an extra copy. - */ - private HTInterval(long intervalStart, long intervalEnd, int attribute, - @NonNull TmfStateValue value, int size) throws TimeRangeException { - if (intervalStart > intervalEnd) { - throw new TimeRangeException("Start:" + intervalStart + ", End:" + intervalEnd); //$NON-NLS-1$ //$NON-NLS-2$ - } - - this.start = intervalStart; - this.end = intervalEnd; - this.attribute = attribute; - this.sv = value; - this.fSizeOnDisk = size; - } - - /** - * Reader factory method. Builds the interval using an already-allocated - * ByteBuffer, which normally comes from a NIO FileChannel. - * - * The interval is just a start, end, attribute and value, this is the - * layout of the HTInterval on disk - *
    - *
  • start (8 bytes)
  • - *
  • end (8 bytes)
  • - *
  • attribute (4 bytes)
  • - *
  • sv type (1 byte)
  • - *
  • sv ( 0 bytes for null, 4 for int , 8 for long and double, and the - * length of the string +2 for strings (it's variable))
  • - *
- * - * @param buffer - * The ByteBuffer from which to read the information - * @return The interval object - * @throws IOException - * If there was an error reading from the buffer - */ - public static final HTInterval readFrom(ByteBuffer buffer) throws IOException { - TmfStateValue value; - - int posStart = buffer.position(); - /* Read the Data Section entry */ - long intervalStart = buffer.getLong(); - long intervalEnd = buffer.getLong(); - int attribute = buffer.getInt(); - - /* Read the 'type' of the value, then react accordingly */ - byte valueType = buffer.get(); - switch (valueType) { - - case TYPE_NULL: - value = TmfStateValue.nullValue(); - break; - - case TYPE_INTEGER: - value = TmfStateValue.newValueInt(buffer.getInt()); - break; - - case TYPE_STRING: { - /* the first short = the size to read */ - int valueSize = buffer.getShort(); - - byte[] array = new byte[valueSize]; - buffer.get(array); - value = TmfStateValue.newValueString(new String(array, CHARSET)); - - /* Confirm the 0'ed byte at the end */ - byte res = buffer.get(); - if (res != 0) { - throw new IOException(errMsg); - } - break; - } - - case TYPE_LONG: - /* Go read the matching entry in the Strings section of the block */ - value = TmfStateValue.newValueLong(buffer.getLong()); - break; - - case TYPE_DOUBLE: - /* Go read the matching entry in the Strings section of the block */ - value = TmfStateValue.newValueDouble(buffer.getDouble()); - break; - - case TYPE_CUSTOM: { - short valueSize = buffer.getShort(); - ISafeByteBufferReader safeBuffer = SafeByteBufferFactory.wrapReader(buffer, valueSize); - value = CustomStateValue.readSerializedValue(safeBuffer); - break; - } - default: - /* Unknown data, better to not make anything up... */ - throw new IOException(errMsg); - } - - try { - return new HTInterval(intervalStart, intervalEnd, attribute, value, buffer.position() - posStart); - } catch (TimeRangeException e) { - throw new IOException(errMsg); - } - } - - /** - * Antagonist of the previous constructor, write the Data entry - * corresponding to this interval in a ByteBuffer (mapped to a block in the - * history-file, hopefully) - * - * The interval is just a start, end, attribute and value, this is the - * layout of the HTInterval on disk - *
    - *
  • start (8 bytes)
  • - *
  • end (8 bytes)
  • - *
  • attribute (4 bytes)
  • - *
  • sv type (1 byte)
  • - *
  • sv ( 0 bytes for null, 4 for int , 8 for long and double, and the - * length of the string +2 for strings (it's variable))
  • - *
- * - * @param buffer - * The already-allocated ByteBuffer corresponding to a SHT Node - */ - public void writeInterval(ByteBuffer buffer) { - final byte byteFromType = getByteFromType(sv.getType()); - - buffer.putLong(start); - buffer.putLong(end); - buffer.putInt(attribute); - buffer.put(byteFromType); - - switch (byteFromType) { - case TYPE_NULL: - break; - case TYPE_INTEGER: - buffer.putInt(sv.unboxInt()); - break; - - case TYPE_STRING: { - String string = sv.unboxStr(); - byte[] strArray = string.getBytes(CHARSET); - - /* - * Write the Strings entry (1st byte = size, then the bytes, then - * the 0). We have checked the string length at the constructor. - */ - buffer.putShort((short) strArray.length); - buffer.put(strArray); - buffer.put((byte) 0); - break; - } - - case TYPE_LONG: - buffer.putLong(sv.unboxLong()); - break; - - case TYPE_DOUBLE: - buffer.putDouble(sv.unboxDouble()); - break; - - case TYPE_CUSTOM: { - int size = ((CustomStateValue) sv).getSerializedSize(); - buffer.putShort((short) size); - ISafeByteBufferWriter safeBuffer = SafeByteBufferFactory.wrapWriter(buffer, size); - ((CustomStateValue) sv).serialize(safeBuffer); - break; - } - - default: - break; - } - } - - @Override - public long getStartTime() { - return start; - } - - @Override - public long getEndTime() { - return end; - } - - @Override - public int getAttribute() { - return attribute; - } - - @Override - public ITmfStateValue getStateValue() { - return sv; - } - - @Override - public boolean intersects(long timestamp) { - if (start <= timestamp) { - if (end >= timestamp) { - return true; - } - } - return false; - } - - /** - * Total serialized size of this interval - * - * @return The interval size - */ - public int getSizeOnDisk() { - return fSizeOnDisk; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - HTInterval other = (HTInterval) obj; - return (start == other.start && - end == other.end && - attribute == other.attribute && - sv.equals(other.sv)); - } - - @Override - public int hashCode() { - return Objects.hash(start, end, attribute, sv); - } - - @Override - public String toString() { - /* Only for debug, should not be externalized */ - StringBuilder sb = new StringBuilder(); - sb.append('['); - sb.append(start); - sb.append(", "); //$NON-NLS-1$ - sb.append(end); - sb.append(']'); - - sb.append(", attribute = "); //$NON-NLS-1$ - sb.append(attribute); - - sb.append(", value = "); //$NON-NLS-1$ - sb.append(sv.toString()); - - return sb.toString(); - } - - /** - * Here we determine how state values "types" are written in the 8-bit field - * that indicates the value type in the file. - */ - private static byte getByteFromType(ITmfStateValue.Type type) { - switch (type) { - case NULL: - return TYPE_NULL; - case INTEGER: - return TYPE_INTEGER; - case STRING: - return TYPE_STRING; - case LONG: - return TYPE_LONG; - case DOUBLE: - return TYPE_DOUBLE; - case CUSTOM: - return TYPE_CUSTOM; - default: - /* Should not happen if the switch is fully covered */ - throw new IllegalStateException(); - } - } -} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HTNode.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HTNode.java deleted file mode 100644 index 776dd44633..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HTNode.java +++ /dev/null @@ -1,674 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010, 2016 Ericsson, École Polytechnique de Montréal, and others - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Alexandre Montplaisir - Initial API and implementation - * Florian Wininger - Add Extension and Leaf Node - * Patrick Tasse - Keep interval list sorted on insert - *******************************************************************************/ - -package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; - -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.FileChannel; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; -import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; -import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue; - -import com.google.common.collect.Iterables; - -/** - * The base class for all the types of nodes that go in the History Tree. - * - * @author Alexandre Montplaisir - */ -public abstract class HTNode { - - // ------------------------------------------------------------------------ - // Class fields - // ------------------------------------------------------------------------ - - /** - * The type of node - */ - public static enum NodeType { - /** - * Core node, which is a "front" node, at any level of the tree except - * the bottom-most one. It has children, and may have extensions. - */ - CORE, - /** - * Leaf node, which is a node at the last bottom level of the tree. It - * cannot have any children or extensions. - */ - LEAF; - - /** - * Determine a node type by reading a serialized byte. - * - * @param rep - * The byte representation of the node type - * @return The corresponding NodeType - * @throws IOException - * If the NodeType is unrecognized - */ - public static NodeType fromByte(byte rep) throws IOException { - switch (rep) { - case 1: - return CORE; - case 2: - return LEAF; - default: - throw new IOException(); - } - } - - /** - * Get the byte representation of this node type. It can then be read - * with {@link #fromByte}. - * - * @return The byte matching this node type - */ - public byte toByte() { - switch (this) { - case CORE: - return 1; - case LEAF: - return 2; - default: - throw new IllegalStateException(); - } - } - } - - /** - *
-     *  1 - byte (type)
-     * 16 - 2x long (start time, end time)
-     * 16 - 3x int (seq number, parent seq number, intervalcount)
-     *  1 - byte (done or not)
-     * 
- */ - private static final int COMMON_HEADER_SIZE = Byte.BYTES - + 2 * Long.BYTES - + 3 * Integer.BYTES - + Byte.BYTES; - - // ------------------------------------------------------------------------ - // Attributes - // ------------------------------------------------------------------------ - - /* Configuration of the History Tree to which belongs this node */ - private final HTConfig fConfig; - - /* Time range of this node */ - private final long fNodeStart; - private long fNodeEnd; - - /* Sequence number = position in the node section of the file */ - private final int fSequenceNumber; - private int fParentSequenceNumber; /* = -1 if this node is the root node */ - - /* Sum of bytes of all intervals in the node */ - private int fSizeOfIntervalSection; - - /* True if this node was read from disk (meaning its end time is now fixed) */ - private volatile boolean fIsOnDisk; - - /* Vector containing all the intervals contained in this node */ - private final List fIntervals; - - /* Lock used to protect the accesses to intervals, nodeEnd and such */ - private final ReentrantReadWriteLock fRwl = new ReentrantReadWriteLock(false); - - /** Order of intervals in a HTNode: sorted by end times, then by start times. */ - private static final Comparator NODE_ORDER = Comparator - .comparingLong(ITmfStateInterval::getEndTime) - .thenComparingLong(ITmfStateInterval::getStartTime) - .thenComparingInt(ITmfStateInterval::getAttribute); - - /** - * Constructor - * - * @param config - * Configuration of the History Tree - * @param seqNumber - * The (unique) sequence number assigned to this particular node - * @param parentSeqNumber - * The sequence number of this node's parent node - * @param start - * The earliest timestamp stored in this node - */ - protected HTNode(HTConfig config, int seqNumber, int parentSeqNumber, long start) { - fConfig = config; - fNodeStart = start; - fSequenceNumber = seqNumber; - fParentSequenceNumber = parentSeqNumber; - - fSizeOfIntervalSection = 0; - fIsOnDisk = false; - fIntervals = new ArrayList<>(); - } - - /** - * Reader factory method. Build a Node object (of the right type) by reading - * a block in the file. - * - * @param config - * Configuration of the History Tree - * @param fc - * FileChannel to the history file, ALREADY SEEKED at the start - * of the node. - * @param nodeFactory - * The factory to create the nodes for this tree - * @return The node object - * @throws IOException - * If there was an error reading from the file channel - */ - public static final @NonNull HTNode readNode(HTConfig config, FileChannel fc, IHistoryTree.IHTNodeFactory nodeFactory) - throws IOException { - HTNode newNode = null; - int res, i; - - ByteBuffer buffer = ByteBuffer.allocate(config.getBlockSize()); - buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.clear(); - res = fc.read(buffer); - assert (res == config.getBlockSize()); - buffer.flip(); - - /* Read the common header part */ - byte typeByte = buffer.get(); - NodeType type = NodeType.fromByte(typeByte); - long start = buffer.getLong(); - long end = buffer.getLong(); - int seqNb = buffer.getInt(); - int parentSeqNb = buffer.getInt(); - int intervalCount = buffer.getInt(); - buffer.get(); // TODO Used to be "isDone", to be removed from the header - - /* Now the rest of the header depends on the node type */ - switch (type) { - case CORE: - /* Core nodes */ - newNode = nodeFactory.createCoreNode(config, seqNb, parentSeqNb, start); - newNode.readSpecificHeader(buffer); - break; - - case LEAF: - /* Leaf nodes */ - newNode = nodeFactory.createLeafNode(config, seqNb, parentSeqNb, start); - newNode.readSpecificHeader(buffer); - break; - - default: - /* Unrecognized node type */ - throw new IOException(); - } - - /* - * At this point, we should be done reading the header and 'buffer' - * should only have the intervals left - */ - for (i = 0; i < intervalCount; i++) { - HTInterval interval = HTInterval.readFrom(buffer); - newNode.fIntervals.add(interval); - newNode.fSizeOfIntervalSection += interval.getSizeOnDisk(); - } - - /* Assign the node's other information we have read previously */ - newNode.fNodeEnd = end; - newNode.fIsOnDisk = true; - - return newNode; - } - - /** - * Write this node to the given file channel. - * - * @param fc - * The file channel to write to (should be sought to be correct - * position) - * @throws IOException - * If there was an error writing - */ - public final void writeSelf(FileChannel fc) throws IOException { - /* - * Yes, we are taking the *read* lock here, because we are reading the - * information in the node to write it to disk. - */ - fRwl.readLock().lock(); - try { - final int blockSize = fConfig.getBlockSize(); - - ByteBuffer buffer = ByteBuffer.allocate(blockSize); - buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.clear(); - - /* Write the common header part */ - buffer.put(getNodeType().toByte()); - buffer.putLong(fNodeStart); - buffer.putLong(fNodeEnd); - buffer.putInt(fSequenceNumber); - buffer.putInt(fParentSequenceNumber); - buffer.putInt(fIntervals.size()); - buffer.put((byte) 1); // TODO Used to be "isDone", to be removed from header - - /* Now call the inner method to write the specific header part */ - writeSpecificHeader(buffer); - - /* Back to us, we write the intervals */ - fIntervals.forEach(i -> i.writeInterval(buffer)); - if (blockSize - buffer.position() != getNodeFreeSpace()) { - throw new IllegalStateException("Wrong free space: Actual: " + (blockSize - buffer.position()) + ", Expected: " + getNodeFreeSpace()); //$NON-NLS-1$ //$NON-NLS-2$ - } - /* - * Fill the rest with zeros - */ - while (buffer.position() < blockSize) { - buffer.put((byte) 0); - } - - /* Finally, write everything in the Buffer to disk */ - buffer.flip(); - int res = fc.write(buffer); - if (res != blockSize) { - throw new IllegalStateException("Wrong size of block written: Actual: " + res + ", Expected: " + blockSize); //$NON-NLS-1$ //$NON-NLS-2$ - } - - } finally { - fRwl.readLock().unlock(); - } - fIsOnDisk = true; - } - - // ------------------------------------------------------------------------ - // Accessors - // ------------------------------------------------------------------------ - - /** - * Retrieve the history tree configuration used for this node. - * - * @return The history tree config - */ - protected HTConfig getConfig() { - return fConfig; - } - - /** - * Get the start time of this node. - * - * @return The start time of this node - */ - public long getNodeStart() { - return fNodeStart; - } - - /** - * Get the end time of this node. - * - * @return The end time of this node - */ - public long getNodeEnd() { - if (fIsOnDisk) { - return fNodeEnd; - } - return 0; - } - - /** - * Get the sequence number of this node. - * - * @return The sequence number of this node - */ - public int getSequenceNumber() { - return fSequenceNumber; - } - - /** - * Get the sequence number of this node's parent. - * - * @return The parent sequence number - */ - public int getParentSequenceNumber() { - return fParentSequenceNumber; - } - - /** - * Change this node's parent. Used when we create a new root node for - * example. - * - * @param newParent - * The sequence number of the node that is the new parent - */ - public void setParentSequenceNumber(int newParent) { - fParentSequenceNumber = newParent; - } - - /** - * Return if this node is "done" (full and written to disk). - * - * @return If this node is done or not - */ - public boolean isOnDisk() { - return fIsOnDisk; - } - - /** - * Add an interval to this node - * - * @param newInterval - * Interval to add to this node - */ - public void addInterval(HTInterval newInterval) { - fRwl.writeLock().lock(); - try { - /* Just in case, should be checked before even calling this function */ - assert (newInterval.getSizeOnDisk() <= getNodeFreeSpace()); - - /* Find the insert position to keep the list sorted */ - int index = 0; - if (!fIntervals.isEmpty()) { - index = Collections.binarySearch(fIntervals, newInterval, NODE_ORDER); - /* - * Interval should not already be in the node, binarySearch will - * return (-insertionPoint - 1). - */ - index = -index - 1; - } - - fIntervals.add(index, newInterval); - fSizeOfIntervalSection += newInterval.getSizeOnDisk(); - - } finally { - fRwl.writeLock().unlock(); - } - } - - /** - * We've received word from the containerTree that newest nodes now exist to - * our right. (Puts isDone = true and sets the endtime) - * - * @param endtime - * The nodeEnd time that the node will have - */ - public void closeThisNode(long endtime) { - fRwl.writeLock().lock(); - try { - /** - * FIXME: was assert (endtime >= fNodeStart); but that exception - * is reached with an empty node that has start time endtime + 1 - */ -// if (endtime < fNodeStart) { -// throw new IllegalArgumentException("Endtime " + endtime + " cannot be lower than start time " + fNodeStart); -// } - - if (!fIntervals.isEmpty()) { - /* - * Make sure there are no intervals in this node with their - * EndTime > the one requested. Only need to check the last one - * since they are sorted - */ - if (endtime < Iterables.getLast(fIntervals).getEndTime()) { - throw new IllegalArgumentException("Closing end time should be greater than or equal to the end time of the intervals of this node"); //$NON-NLS-1$ - } - } - - fNodeEnd = endtime; - } finally { - fRwl.writeLock().unlock(); - } - } - - /** - * The method to fill up the stateInfo (passed on from the Current State - * Tree when it does a query on the SHT). We'll replace the data in that - * vector with whatever relevant we can find from this node - * - * @param stateInfo - * The same stateInfo that comes from SHT's doQuery() - * @param t - * The timestamp for which the query is for. Only return - * intervals that intersect t. - * @throws TimeRangeException - * If 't' is invalid - */ - public void writeInfoFromNode(List stateInfo, long t) - throws TimeRangeException { - /* This is from a state system query, we are "reading" this node */ - fRwl.readLock().lock(); - try { - for (int i = getStartIndexFor(t); i < fIntervals.size(); i++) { - /* - * Now we only have to compare the Start times, since we now the - * End times necessarily fit. - * - * Second condition is to ignore new attributes that might have - * been created after stateInfo was instantiated (they would be - * null anyway). - */ - ITmfStateInterval interval = fIntervals.get(i); - if (t >= interval.getStartTime() && - interval.getAttribute() < stateInfo.size()) { - stateInfo.set(interval.getAttribute(), interval); - } - } - } finally { - fRwl.readLock().unlock(); - } - } - - /** - * Get a single Interval from the information in this node If the - * key/timestamp pair cannot be found, we return null. - * - * @param key - * The attribute quark to look for - * @param t - * The timestamp - * @return The Interval containing the information we want, or null if it - * wasn't found - * @throws TimeRangeException - * If 't' is invalid - */ - public HTInterval getRelevantInterval(int key, long t) throws TimeRangeException { - fRwl.readLock().lock(); - try { - for (int i = getStartIndexFor(t); i < fIntervals.size(); i++) { - HTInterval curInterval = fIntervals.get(i); - if (curInterval.getAttribute() == key - && curInterval.getStartTime() <= t) { - return curInterval; - } - } - - /* We didn't find the relevant information in this node */ - return null; - - } finally { - fRwl.readLock().unlock(); - } - } - - private int getStartIndexFor(long t) throws TimeRangeException { - /* Should only be called by methods with the readLock taken */ - - if (fIntervals.isEmpty()) { - return 0; - } - - /* - * Since the intervals are sorted by end time then by start time, we can - * skip all the ones at the beginning whose end times are smaller than - * 't'. We search for a dummy interval from [Long.MIN_VALUE, t], which - * will return the first interval that ends with a time >= t. - */ - HTInterval dummy = new HTInterval(Long.MIN_VALUE, t, 0, TmfStateValue.nullValue()); - int index = Collections.binarySearch(fIntervals, dummy, NODE_ORDER); - - /* Handle negative binarySearch return */ - return (index >= 0 ? index : -index - 1); - } - - /** - * Return the total header size of this node (will depend on the node type). - * - * @return The total header size - */ - public final int getTotalHeaderSize() { - return COMMON_HEADER_SIZE + getSpecificHeaderSize(); - } - - /** - * @return The offset, within the node, where the Data section ends - */ - private int getDataSectionEndOffset() { - return getTotalHeaderSize() + fSizeOfIntervalSection; - } - - /** - * Returns the free space in the node, which is simply put, the - * stringSectionOffset - dataSectionOffset - * - * @return The amount of free space in the node (in bytes) - */ - public int getNodeFreeSpace() { - fRwl.readLock().lock(); - int ret = fConfig.getBlockSize() - getDataSectionEndOffset(); - fRwl.readLock().unlock(); - - return ret; - } - - /** - * Returns the current space utilization of this node, as a percentage. - * (used space / total usable space, which excludes the header) - * - * @return The percentage (value between 0 and 100) of space utilization in - * in this node. - */ - public long getNodeUsagePercent() { - fRwl.readLock().lock(); - try { - final int blockSize = fConfig.getBlockSize(); - float freePercent = (float) getNodeFreeSpace() - / (float) (blockSize - getTotalHeaderSize()) - * 100F; - return (long) (100L - freePercent); - - } finally { - fRwl.readLock().unlock(); - } - } - - /** - * @name Debugging functions - */ - - @SuppressWarnings("nls") - @Override - public String toString() { - /* Only used for debugging, shouldn't be externalized */ - return String.format("Node #%d, %s, %s, %d intervals (%d%% used), [%d - %s]", - fSequenceNumber, - (fParentSequenceNumber == -1) ? "Root" : "Parent #" + fParentSequenceNumber, - toStringSpecific(), - fIntervals.size(), - getNodeUsagePercent(), - fNodeStart, - (fIsOnDisk || fNodeEnd != 0) ? fNodeEnd : "..."); - } - - /** - * Debugging function that prints out the contents of this node - * - * @param writer - * PrintWriter in which we will print the debug output - */ - @SuppressWarnings("nls") - public void debugPrintIntervals(PrintWriter writer) { - /* Only used for debugging, shouldn't be externalized */ - writer.println("Intervals for node #" + fSequenceNumber + ":"); - - /* Array of children */ - if (getNodeType() != NodeType.LEAF) { /* Only Core Nodes can have children */ - ParentNode thisNode = (ParentNode) this; - writer.print(" " + thisNode.getNbChildren() + " children"); - if (thisNode.getNbChildren() >= 1) { - writer.print(": [ " + thisNode.getChild(0)); - for (int i = 1; i < thisNode.getNbChildren(); i++) { - writer.print(", " + thisNode.getChild(i)); - } - writer.print(']'); - } - writer.print('\n'); - } - - /* List of intervals in the node */ - writer.println(" Intervals contained:"); - for (int i = 0; i < fIntervals.size(); i++) { - writer.println(fIntervals.get(i).toString()); - } - writer.println('\n'); - } - - // ------------------------------------------------------------------------ - // Abstract methods - // ------------------------------------------------------------------------ - - /** - * Get the byte value representing the node type. - * - * @return The node type - */ - public abstract NodeType getNodeType(); - - /** - * Return the specific header size of this node. This means the size - * occupied by the type-specific section of the header (not counting the - * common part). - * - * @return The specific header size - */ - protected abstract int getSpecificHeaderSize(); - - /** - * Read the type-specific part of the node header from a byte buffer. - * - * @param buffer - * The byte buffer to read from. It should be already positioned - * correctly. - */ - protected abstract void readSpecificHeader(ByteBuffer buffer); - - /** - * Write the type-specific part of the header in a byte buffer. - * - * @param buffer - * The buffer to write to. It should already be at the correct - * position. - */ - protected abstract void writeSpecificHeader(ByteBuffer buffer); - - /** - * Node-type-specific toString method. Used for debugging. - * - * @return A string representing the node - */ - protected abstract String toStringSpecific(); -} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HT_IO.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HT_IO.java deleted file mode 100644 index b26225b733..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HT_IO.java +++ /dev/null @@ -1,308 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012, 2014 Ericsson - * Copyright (c) 2010, 2011 École Polytechnique de Montréal - * Copyright (c) 2010, 2011 Alexandre Montplaisir - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - *******************************************************************************/ - -package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; - -import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.channels.ClosedChannelException; -import java.nio.channels.FileChannel; -import java.util.logging.Logger; - -import org.eclipse.jdt.annotation.NonNull; -import java.util.Objects; -import java.util.concurrent.ExecutionException; - -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.tracecompass.common.core.log.TraceCompassLog; -import org.eclipse.tracecompass.internal.statesystem.core.Activator; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.IHistoryTree.IHTNodeFactory; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; - -/** - * This class abstracts inputs/outputs of the HistoryTree nodes. - * - * It contains all the methods and descriptors to handle reading/writing nodes - * to the tree-file on disk and all the caching mechanisms. - * - * This abstraction is mainly for code isolation/clarification purposes. Every - * HistoryTree must contain 1 and only 1 HT_IO element. - * - * @author Alexandre Montplaisir - */ -public class HT_IO { - - private static final Logger LOGGER = TraceCompassLog.getLogger(HT_IO.class); - - // ------------------------------------------------------------------------ - // Global cache of nodes - // ------------------------------------------------------------------------ - - private static final class CacheKey { - - public final HT_IO fStateHistory; - public final int fSeqNumber; - - public CacheKey(HT_IO stateHistory, int seqNumber) { - fStateHistory = stateHistory; - fSeqNumber = seqNumber; - } - - @Override - public int hashCode() { - return Objects.hash(fStateHistory, fSeqNumber); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - CacheKey other = (CacheKey) obj; - return (fStateHistory.equals(other.fStateHistory) && - fSeqNumber == other.fSeqNumber); - } - } - - private static final int CACHE_SIZE = 256; - - private static final LoadingCache NODE_CACHE = - checkNotNull(CacheBuilder.newBuilder() - .maximumSize(CACHE_SIZE) - .build(new CacheLoader() { - @Override - public HTNode load(CacheKey key) throws IOException { - HT_IO io = key.fStateHistory; - int seqNb = key.fSeqNumber; - - LOGGER.finest(() -> "[HtIo:CacheMiss] seqNum=" + seqNb); //$NON-NLS-1$ - - synchronized (io) { - io.seekFCToNodePos(io.fFileChannelIn, seqNb); - return HTNode.readNode(io.fConfig, io.fFileChannelIn, key.fStateHistory.fNodeFactory); - } - } - })); - - - // ------------------------------------------------------------------------ - // Instance fields - // ------------------------------------------------------------------------ - - /* Configuration of the History Tree */ - private final HTConfig fConfig; - - /* Fields related to the file I/O */ - private final FileInputStream fFileInputStream; - private final FileOutputStream fFileOutputStream; - private final FileChannel fFileChannelIn; - private final FileChannel fFileChannelOut; - - private final IHTNodeFactory fNodeFactory; - - // ------------------------------------------------------------------------ - // Methods - // ------------------------------------------------------------------------ - - - - /** - * Standard constructor - * - * @param config - * The configuration object for the StateHistoryTree - * @param newFile - * Flag indicating that the file must be created from scratch - * @param nodeFactory - * The factory to create new nodes for this tree - * - * @throws IOException - * An exception can be thrown when file cannot be accessed - */ - public HT_IO(HTConfig config, boolean newFile, IHTNodeFactory nodeFactory) throws IOException { - fConfig = config; - - File historyTreeFile = config.getStateFile(); - if (newFile) { - boolean success1 = true; - /* Create a new empty History Tree file */ - if (historyTreeFile.exists()) { - success1 = historyTreeFile.delete(); - } - boolean success2 = historyTreeFile.createNewFile(); - if (!(success1 && success2)) { - /* It seems we do not have permission to create the new file */ - throw new IOException("Cannot create new file at " + //$NON-NLS-1$ - historyTreeFile.getName()); - } - fFileInputStream = new FileInputStream(historyTreeFile); - fFileOutputStream = new FileOutputStream(historyTreeFile, false); - } else { - /* - * We want to open an existing file, make sure we don't squash the - * existing content when opening the fos! - */ - fFileInputStream = new FileInputStream(historyTreeFile); - fFileOutputStream = new FileOutputStream(historyTreeFile, true); - } - fFileChannelIn = fFileInputStream.getChannel(); - fFileChannelOut = fFileOutputStream.getChannel(); - fNodeFactory = nodeFactory; - } - - /** - * Read a node from the file on disk. - * - * @param seqNumber - * The sequence number of the node to read. - * @return The object representing the node - * @throws ClosedChannelException - * Usually happens because the file was closed while we were - * reading. Instead of using a big reader-writer lock, we'll - * just catch this exception. - */ - public @NonNull HTNode readNode(int seqNumber) throws ClosedChannelException { - /* Do a cache lookup. If it's not present it will be loaded from disk */ - LOGGER.finest(() -> "[HtIo:CacheLookup] seqNum=" + seqNumber); //$NON-NLS-1$ - CacheKey key = new CacheKey(this, seqNumber); - try { - return checkNotNull(NODE_CACHE.get(key)); - - } catch (ExecutionException e) { - /* Get the inner exception that was generated */ - Throwable cause = e.getCause(); - if (cause instanceof ClosedChannelException) { - throw (ClosedChannelException) cause; - } - /* - * Other types of IOExceptions shouldn't happen at this point though. - */ - Activator.getDefault().logError(e.getMessage(), e); - throw new IllegalStateException(); - } - } - - /** - * Write the given node to disk. - * - * @param node - * The node to write. - */ - public void writeNode(HTNode node) { - try { - int seqNumber = node.getSequenceNumber(); - - /* "Write-back" the node into the cache */ - CacheKey key = new CacheKey(this, seqNumber); - NODE_CACHE.put(key, node); - - /* Position ourselves at the start of the node and write it */ - synchronized (this) { - seekFCToNodePos(fFileChannelOut, seqNumber); - node.writeSelf(fFileChannelOut); - } - } catch (IOException e) { - /* If we were able to open the file, we should be fine now... */ - Activator.getDefault().logError(e.getMessage(), e); - } - } - - /** - * Get the output file channel, used for writing. - * - * @return The output file channel - */ - public FileChannel getFcOut() { - return fFileChannelOut; - } - - /** - * Retrieve the input stream with which to write the attribute tree. - * - * @param nodeOffset - * The offset in the file, in number of nodes. This should be - * after all the nodes. - * @return The correctly-seeked input stream - */ - public FileInputStream supplyATReader(int nodeOffset) { - try { - /* - * Position ourselves at the start of the Mapping section in the - * file (which is right after the Blocks) - */ - seekFCToNodePos(fFileChannelIn, nodeOffset); - } catch (IOException e) { - Activator.getDefault().logError(e.getMessage(), e); - } - return fFileInputStream; - } - - /** - * Close all file channels and streams. - */ - public synchronized void closeFile() { - try { - fFileInputStream.close(); - fFileOutputStream.close(); - } catch (IOException e) { - Activator.getDefault().logError(e.getMessage(), e); - } - } - - /** - * Delete the history tree file - */ - public synchronized void deleteFile() { - closeFile(); - - File historyTreeFile = fConfig.getStateFile(); - if (!historyTreeFile.delete()) { - /* We didn't succeed in deleting the file */ - Activator.getDefault().logError("Failed to delete" + historyTreeFile.getName()); //$NON-NLS-1$ - } - } - - /** - * Seek the given FileChannel to the position corresponding to the node that - * has seqNumber - * - * @param fc - * the channel to seek - * @param seqNumber - * the node sequence number to seek the channel to - * @throws IOException - * If some other I/O error occurs - */ - private void seekFCToNodePos(FileChannel fc, int seqNumber) - throws IOException { - /* - * Cast to (long) is needed to make sure the result is a long too and - * doesn't get truncated - */ - fc.position(IHistoryTree.TREE_HEADER_SIZE - + ((long) seqNumber) * fConfig.getBlockSize()); - } - -} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HistoryTreeBackend.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HistoryTreeBackend.java index 5e752deeb9..2a6b19d9b3 100644 --- a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HistoryTreeBackend.java +++ b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HistoryTreeBackend.java @@ -18,15 +18,13 @@ package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.nio.channels.ClosedChannelException; -import java.util.Deque; -import java.util.LinkedList; import java.util.List; import java.util.logging.Logger; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.tracecompass.common.core.log.TraceCompassLog; -import org.eclipse.tracecompass.internal.statesystem.core.Activator; +import org.eclipse.tracecompass.internal.provisional.datastore.core.condition.RangeCondition; +import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.IHistoryTree; import org.eclipse.tracecompass.statesystem.core.backend.IStateHistoryBackend; import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException; import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; @@ -35,6 +33,7 @@ import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue; import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Iterables; /** * History Tree backend for storing a state history. This is the basic version @@ -51,7 +50,7 @@ public class HistoryTreeBackend implements IStateHistoryBackend { /** * The history tree that sits underneath. */ - private final @NonNull IHistoryTree fSht; + private final @NonNull IHistoryTree<@NonNull StateSystemInterval> fSht; /** Indicates if the history tree construction is done */ private volatile boolean fFinishedBuilding = false; @@ -99,15 +98,18 @@ public class HistoryTreeBackend implements IStateHistoryBackend { * Thrown if we can't create the file for some reason */ public HistoryTreeBackend(@NonNull String ssid, - File newStateFile, + @NonNull File newStateFile, int providerVersion, long startTime, int blockSize, int maxChildren) throws IOException { + fSsid = ssid; - final HTConfig conf = new HTConfig(newStateFile, blockSize, maxChildren, - providerVersion, startTime); - fSht = initializeSHT(conf); + fSht = initializeSHT(newStateFile, + blockSize, + maxChildren, + providerVersion, + startTime); } /** @@ -130,7 +132,8 @@ public class HistoryTreeBackend implements IStateHistoryBackend { * Thrown if we can't create the file for some reason * @since 1.0 */ - public HistoryTreeBackend(@NonNull String ssid, File newStateFile, int providerVersion, long startTime) + public HistoryTreeBackend(@NonNull String ssid, @NonNull File newStateFile, + int providerVersion, long startTime) throws IOException { this(ssid, newStateFile, providerVersion, startTime, 64 * 1024, 50); } @@ -157,18 +160,40 @@ public class HistoryTreeBackend implements IStateHistoryBackend { } /** - * New-tree initializer for the History Tree wrapped by this backend. Can be - * overriden to use different implementations. + * Instantiate the new history tree to be used. * - * @param conf - * The HTConfig configuration object + * @param stateHistoryFile + * The name of the history file + * @param blockSize + * The size of each "block" on disk in bytes. One node will + * always fit in one block. It should be at least 4096. + * @param maxChildren + * The maximum number of children allowed per core (non-leaf) + * node. + * @param providerVersion + * The version of the state provider. If a file already exists, + * and their versions match, the history file will not be rebuilt + * uselessly. + * @param treeStart + * The start time of the history * @return The new history tree * @throws IOException - * If there was a problem during creation + * If an error happens trying to open/write to the file + * specified in the config */ @VisibleForTesting - protected @NonNull IHistoryTree initializeSHT(@NonNull HTConfig conf) throws IOException { - return HistoryTreeFactory.createHistoryTree(conf); + protected @NonNull IHistoryTree<@NonNull StateSystemInterval> initializeSHT( + @NonNull File stateHistoryFile, + int blockSize, + int maxChildren, + int providerVersion, + long treeStart) throws IOException { + + return HistoryTreeFactory.createHistoryTree(stateHistoryFile, + blockSize, + maxChildren, + providerVersion, + treeStart); } /** @@ -184,7 +209,8 @@ public class HistoryTreeBackend implements IStateHistoryBackend { * If there was a problem during creation */ @VisibleForTesting - protected @NonNull IHistoryTree initializeSHT(@NonNull File existingStateFile, int providerVersion) throws IOException { + protected @NonNull IHistoryTree<@NonNull StateSystemInterval>initializeSHT( + @NonNull File existingStateFile, int providerVersion) throws IOException { return HistoryTreeFactory.createFromFile(existingStateFile.toPath(), providerVersion); } @@ -197,7 +223,7 @@ public class HistoryTreeBackend implements IStateHistoryBackend { * * @return The history tree */ - protected final @NonNull IHistoryTree getSHT() { + protected final @NonNull IHistoryTree<@NonNull StateSystemInterval> getSHT() { return fSht; } @@ -219,11 +245,11 @@ public class HistoryTreeBackend implements IStateHistoryBackend { @Override public void insertPastState(long stateStartTime, long stateEndTime, int quark, ITmfStateValue value) throws TimeRangeException { - HTInterval interval = new HTInterval(stateStartTime, stateEndTime, + StateSystemInterval interval = new StateSystemInterval(stateStartTime, stateEndTime, quark, (TmfStateValue) value); /* Start insertions at the "latest leaf" */ - getSHT().insertInterval(interval); + getSHT().insert(interval); } @Override @@ -272,41 +298,24 @@ public class HistoryTreeBackend implements IStateHistoryBackend { throws TimeRangeException, StateSystemDisposedException { checkValidTime(t); - /* Queue is a stack of nodes containing nodes intersecting t */ - Deque queue = new LinkedList<>(); - - /* We start by reading the information in the root node */ - queue.add(getSHT().getRootNode().getSequenceNumber()); - - /* Then we follow the down in the relevant children */ - try { - while (!queue.isEmpty()) { - int sequenceNumber = queue.pop(); - HTNode currentNode = getSHT().readNode(sequenceNumber); - if (currentNode.getNodeType() == HTNode.NodeType.CORE) { - /* Here we add the relevant children nodes for BFS */ - queue.addAll(((ParentNode) currentNode).selectNextChildren(t)); - } - currentNode.writeInfoFromNode(stateInfo, t); - } - } catch (ClosedChannelException e) { - throw new StateSystemDisposedException(e); - } + RangeCondition<@NonNull Long> rc = RangeCondition.singleton(t); - /* - * The stateInfo should now be filled with everything needed, we pass - * the control back to the State System. - */ + Iterable intervals = getSHT().getMatchingIntervals(rc, e -> true); + Iterables.filter(intervals, e -> e.getAttribute() < stateInfo.size()) + .forEach(e -> stateInfo.set(e.getAttribute(), e)); } @Override public ITmfStateInterval doSingularQuery(long t, int attributeQuark) throws TimeRangeException, StateSystemDisposedException { - try { - return getRelevantInterval(t, attributeQuark); - } catch (ClosedChannelException e) { - throw new StateSystemDisposedException(e); - } + checkValidTime(t); + + RangeCondition<@NonNull Long> rc = RangeCondition.singleton(t); + + Iterable intervals = getSHT() + .getMatchingIntervals(rc, e -> e.getAttribute() == attributeQuark); + + return Iterables.getFirst(intervals, null); } private void checkValidTime(long t) { @@ -318,29 +327,6 @@ public class HistoryTreeBackend implements IStateHistoryBackend { } } - /** - * Inner method to find the interval in the tree containing the requested - * key/timestamp pair, wherever in which node it is. - */ - private HTInterval getRelevantInterval(long t, int key) - throws TimeRangeException, ClosedChannelException { - checkValidTime(t); - - Deque queue = new LinkedList<>(); - queue.add(getSHT().getRootNode().getSequenceNumber()); - HTInterval interval = null; - while (interval == null && !queue.isEmpty()) { - int sequenceNumber = queue.pop(); - HTNode currentNode = getSHT().readNode(sequenceNumber); - if (currentNode.getNodeType() == HTNode.NodeType.CORE) { - /* Here we add the relevant children nodes for BFS */ - queue.addAll(((ParentNode) currentNode).selectNextChildren(t)); - } - interval = currentNode.getRelevantInterval(key, t); - } - return interval; - } - /** * Return the size of the tree history file * @@ -356,25 +342,28 @@ public class HistoryTreeBackend implements IStateHistoryBackend { * @return Average node usage % */ public int getAverageNodeUsage() { - HTNode node; - long total = 0; - long ret; - - try { - for (int seq = 0; seq < getSHT().getNodeCount(); seq++) { - node = getSHT().readNode(seq); - total += node.getNodeUsagePercent(); - } - } catch (ClosedChannelException e) { - Activator.getDefault().logError(e.getMessage(), e); - } - - ret = total / getSHT().getNodeCount(); - /* The return value should be a percentage */ - if (ret < 0 || ret > 100) { - throw new IllegalStateException("Average node usage is not a percentage: " + ret); //$NON-NLS-1$ - } - return (int) ret; + return 0; + // TODO Reimplement, elsewhere? + +// HTNode node; +// long total = 0; +// long ret; +// +// try { +// for (int seq = 0; seq < getSHT().getNodeCount(); seq++) { +// node = getSHT().readNode(seq); +// total += node.getNodeUsagePercent(); +// } +// } catch (ClosedChannelException e) { +// Activator.getDefault().logError(e.getMessage(), e); +// } +// +// ret = total / getSHT().getNodeCount(); +// /* The return value should be a percentage */ +// if (ret < 0 || ret > 100) { +// throw new IllegalStateException("Average node usage is not a percentage: " + ret); //$NON-NLS-1$ +// } +// return (int) ret; } } diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HistoryTreeFactory.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HistoryTreeFactory.java index 31d4487d97..c52797a4f7 100644 --- a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HistoryTreeFactory.java +++ b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/HistoryTreeFactory.java @@ -9,6 +9,7 @@ package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; +import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -18,7 +19,8 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.classic.HistoryTreeClassic; +import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.IHistoryTree; +import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.classic.ClassicHistoryTree; /** * Class that contains factory methods to build different types of history trees @@ -32,18 +34,40 @@ public final class HistoryTreeFactory { } /** - * Create a new State History from scratch, using a {@link HTConfig} object - * for configuration. + * Create a new History Tree from scratch, specifying all configuration + * parameters. * - * @param conf - * The config to use for this History Tree. - * @return the new state history tree + * @param stateHistoryFile + * The name of the history file + * @param blockSize + * The size of each "block" on disk in bytes. One node will + * always fit in one block. It should be at least 4096. + * @param maxChildren + * The maximum number of children allowed per core (non-leaf) + * node. + * @param providerVersion + * The version of the state provider. If a file already exists, + * and their versions match, the history file will not be rebuilt + * uselessly. + * @param treeStart + * The start time of the history + * @return The new history tree * @throws IOException * If an error happens trying to open/write to the file * specified in the config */ - public static IHistoryTree createHistoryTree(HTConfig conf) throws IOException { - return new HistoryTreeClassic(conf); + public static IHistoryTree createHistoryTree(File stateHistoryFile, + int blockSize, + int maxChildren, + int providerVersion, + long treeStart) throws IOException { + + return new ClassicHistoryTree<>(stateHistoryFile, + blockSize, + maxChildren, + providerVersion, + treeStart, + StateSystemInterval.DESERIALISER); } /** @@ -58,7 +82,8 @@ public final class HistoryTreeFactory { * @throws IOException * If an error happens reading the file */ - public static IHistoryTree createFromFile(Path existingStateFile, int expectedProviderVersion) throws IOException { + public static IHistoryTree createFromFile( + Path existingStateFile, int expectedProviderVersion) throws IOException { /* * Check the file exists and has a positive length. These verifications * will also be done in the HT's constructor. @@ -84,8 +109,9 @@ public final class HistoryTreeFactory { */ int magicNumber = buffer.getInt(); switch (magicNumber) { - case HistoryTreeClassic.HISTORY_FILE_MAGIC_NUMBER: - return new HistoryTreeClassic(existingStateFile.toFile(), expectedProviderVersion); + case ClassicHistoryTree.HISTORY_FILE_MAGIC_NUMBER: + return new ClassicHistoryTree<>(existingStateFile.toFile(), + expectedProviderVersion, StateSystemInterval.DESERIALISER); default: throw new IOException("Not a known history tree file"); //$NON-NLS-1$ } diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/IHistoryTree.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/IHistoryTree.java deleted file mode 100644 index 299db35370..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/IHistoryTree.java +++ /dev/null @@ -1,199 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010, 2016 Ericsson, École Polytechnique de Montréal, and others - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - *******************************************************************************/ - -package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.channels.ClosedChannelException; -import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; - -/** - * Meta-container for the History Tree. This structure contains all the - * high-level data relevant to the tree. - * - * @author Alexandre Montplaisir - * @author Geneviève Bastien - */ -public interface IHistoryTree { - - /** - * Interface for history to create the various HTNodes - */ - interface IHTNodeFactory { - - /** - * Creates a new core node for the specific history tree - * - * @param config - * Configuration of the History Tree - * @param seqNumber - * The (unique) sequence number assigned to this particular - * node - * @param parentSeqNumber - * The sequence number of this node's parent node - * @param start - * The earliest timestamp stored in this node - * @return The new core node - * @throws IOException - * any exception occurring while trying to read/create the - * node - */ - HTNode createCoreNode(HTConfig config, int seqNumber, int parentSeqNumber, long start) throws IOException; - - /** - * Creates a new core node for the specific history tree - * - * @param config - * Configuration of the History Tree - * @param seqNumber - * The (unique) sequence number assigned to this particular - * node - * @param parentSeqNumber - * The sequence number of this node's parent node - * @param start - * The earliest timestamp stored in this node - * @return The new core node - * @throws IOException - * any exception occurring while trying to read/create the - * node - */ - HTNode createLeafNode(HTConfig config, int seqNumber, int parentSeqNumber, long start) throws IOException; - } - - /** - * Size of the "tree header" in the tree-file The nodes will use this offset - * to know where they should be in the file. This should always be a - * multiple of 4K. - */ - int TREE_HEADER_SIZE = 4096; - - /** - * "Save" the tree to disk. This method will cause the treeIO object to - * commit all nodes to disk and then return the RandomAccessFile descriptor - * so the Tree object can save its configuration into the header of the - * file. - * - * @param requestedEndTime - * The greatest timestamp present in the history tree - */ - void closeTree(long requestedEndTime); - - // ------------------------------------------------------------------------ - // Accessors - // ------------------------------------------------------------------------ - - /** - * Get the start time of this tree. - * - * @return The start time - */ - long getTreeStart(); - - /** - * Get the current end time of this tree. - * - * @return The end time - */ - long getTreeEnd(); - - /** - * Get the number of nodes in this tree. - * - * @return The number of nodes - */ - int getNodeCount(); - - /** - * Get the current root node of this tree - * - * @return The root node - */ - HTNode getRootNode(); - - // ------------------------------------------------------------------------ - // HT_IO interface - // ------------------------------------------------------------------------ - - /** - * Return the FileInputStream reader with which we will read an attribute - * tree (it will be sought to the correct position). - * - * @return The FileInputStream indicating the file and position from which - * the attribute tree can be read. - */ - FileInputStream supplyATReader(); - - /** - * Return the file to which we will write the attribute tree. - * - * @return The file to which we will write the attribute tree - */ - File supplyATWriterFile(); - - /** - * Return the position in the file (given by {@link #supplyATWriterFile}) - * where to start writing the attribute tree. - * - * @return The position in the file where to start writing - */ - long supplyATWriterFilePos(); - - /** - * Read a node from the tree. - * - * @param seqNumber - * The sequence number of the node to read - * @return The node - * @throws ClosedChannelException - * If the tree IO is unavailable - */ - HTNode readNode(int seqNumber) throws ClosedChannelException; - - /** - * Write a node object to the history file. - * - * @param node - * The node to write to disk - */ - void writeNode(HTNode node); - - /** - * Close the history file. - */ - void closeFile(); - - /** - * Delete the history file. - */ - void deleteFile(); - - // ------------------------------------------------------------------------ - // Operations - // ------------------------------------------------------------------------ - - /** - * Insert an interval in the tree. - * - * @param interval - * The interval to be inserted - * @throws TimeRangeException - * If the start of end time of the interval are invalid - */ - void insertInterval(HTInterval interval) throws TimeRangeException; - - /** - * Get the current size of the history file. - * - * @return The history file size - */ - long getFileSize(); - -} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/LeafNode.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/LeafNode.java deleted file mode 100644 index d4cecf39d8..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/LeafNode.java +++ /dev/null @@ -1,71 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 École Polytechnique de Montréal and others - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Florian Wininger - Initial API and implementation - *******************************************************************************/ - -package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; - -import java.nio.ByteBuffer; - -/** - * A Leaf node is a last-level node of a History Tree. - * - * A leaf node cannot have children, so it extends HTNode without adding - * anything in particular. - * - * @author Florian Wininger - */ -public final class LeafNode extends HTNode { - - /** - * Initial constructor. Use this to initialize a new EMPTY node. - * - * @param config - * Configuration of the History Tree - * @param seqNumber - * The (unique) sequence number assigned to this particular node - * @param parentSeqNumber - * The sequence number of this node's parent node - * @param start - * The earliest timestamp stored in this node - */ - public LeafNode(HTConfig config, int seqNumber, int parentSeqNumber, - long start) { - super(config, seqNumber, parentSeqNumber, start); - } - - @Override - protected void readSpecificHeader(ByteBuffer buffer) { - /* No specific header part */ - } - - @Override - protected void writeSpecificHeader(ByteBuffer buffer) { - /* No specific header part */ - } - - @Override - public NodeType getNodeType() { - return NodeType.LEAF; - } - - @Override - protected int getSpecificHeaderSize() { - /* Empty */ - return 0; - } - - @Override - public String toStringSpecific() { - /* Only used for debugging, shouldn't be externalized */ - return "Leaf Node"; //$NON-NLS-1$; - } - -} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/ParentNode.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/ParentNode.java deleted file mode 100644 index 15003bdf4a..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/ParentNode.java +++ /dev/null @@ -1,96 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010, 2016 Ericsson, École Polytechnique de Montréal, and others - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - *******************************************************************************/ - -package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; - -import java.util.Collection; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; - -/** - * A Core node is a first-level node of a History Tree which is not a leaf node. - * - * It extends HTNode by adding support for child nodes, and also extensions. - * - * @author Alexandre Montplaisir - * @author Florian Wininger - */ -public abstract class ParentNode extends HTNode { - - /** - * Initial constructor. Use this to initialize a new EMPTY node. - * - * @param config - * Configuration of the History Tree - * @param seqNumber - * The (unique) sequence number assigned to this particular node - * @param parentSeqNumber - * The sequence number of this node's parent node - * @param start - * The earliest timestamp stored in this node - */ - public ParentNode(HTConfig config, int seqNumber, int parentSeqNumber, - long start) { - super(config, seqNumber, parentSeqNumber, start); - } - - /** - * Return the number of child nodes this node has. - * - * @return The number of child nodes - */ - public abstract int getNbChildren(); - - /** - * Get the child node corresponding to the specified index - * - * @param index The index of the child to lookup - * @return The child node - */ - public abstract int getChild(int index); - - /** - * Get the latest (right-most) child node of this node. - * - * @return The latest child node - */ - public abstract int getLatestChild(); - - /** - * Get the start time of the specified child node. - * - * @param index - * The index of the child node - * @return The start time of the that child node. - */ - public abstract long getChildStart(int index); - - /** - * Tell this node that it has a new child (Congrats!) - * - * @param childNode - * The SHTNode object of the new child - */ - public abstract void linkNewChild(HTNode childNode); - - /** - * Inner method to select the sequence numbers for the children of the - * current node that intersect the given timestamp. Useful for moving down - * the tree. - * - * @param t - * The timestamp to choose which child is the next one - * @return Collection of sequence numbers of the child nodes that intersect - * t, non-null empty collection if this is a Leaf Node - * @throws TimeRangeException - * If t is out of the node's range - */ - public abstract @NonNull Collection<@NonNull Integer> selectNextChildren(long t); - -} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/StateSystemInterval.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/StateSystemInterval.java new file mode 100644 index 0000000000..cc99e8b7bb --- /dev/null +++ b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/StateSystemInterval.java @@ -0,0 +1,342 @@ +/******************************************************************************* + * Copyright (c) 2012, 2015 Ericsson, École Polytechnique de Montréal + * Copyright (c) 2010, 2011 Alexandre Montplaisir + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Alexandre Montplaisir - Initial API and implementation + * Florian Wininger - Allow to change the size of a interval + * Patrick Tasse - Add message to exceptions + *******************************************************************************/ + +package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; + +import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; + +import java.nio.charset.Charset; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.tracecompass.internal.provisional.datastore.core.interval.HTInterval; +import org.eclipse.tracecompass.internal.provisional.datastore.core.interval.IHTIntervalReader; +import org.eclipse.tracecompass.internal.provisional.datastore.core.serialization.ISafeByteBufferWriter; +import org.eclipse.tracecompass.internal.provisional.statesystem.core.statevalue.CustomStateValue; +import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; +import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; +import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue; +import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue; + +/** + * The interval component, which will be contained in a node of the History + * Tree. + * + * @author Alexandre Montplaisir + */ +public final class StateSystemInterval extends HTInterval implements ITmfStateInterval { + + private static final Charset CHARSET = Charset.forName("UTF-8"); //$NON-NLS-1$ + + private static final String errMsg = "Invalid interval data. Maybe your file is corrupt?"; //$NON-NLS-1$ + + /* 'Byte' equivalent for state values types */ + private static final byte TYPE_NULL = -1; + private static final byte TYPE_INTEGER = 0; + private static final byte TYPE_STRING = 1; + private static final byte TYPE_LONG = 2; + private static final byte TYPE_DOUBLE = 3; + private static final byte TYPE_CUSTOM = 20; + + private final int fAttribute; + private final @NonNull TmfStateValue fSv; + + /** Number of bytes used by this interval when it is written to disk */ + private final int fSizeOnDisk; + + /** + * Standard constructor + * + * @param intervalStart + * Start time of the interval + * @param intervalEnd + * End time of the interval + * @param attribute + * Attribute (quark) to which the state represented by this + * interval belongs + * @param value + * State value represented by this interval + * @throws TimeRangeException + * If the start time or end time are invalid + */ + public StateSystemInterval(long intervalStart, long intervalEnd, int attribute, + @NonNull TmfStateValue value) throws TimeRangeException { + super(intervalStart, intervalEnd); + + fAttribute = attribute; + fSv = value; + fSizeOnDisk = computeSizeOnDisk(fSv); + } + + /** + * Compute how much space (in bytes) an interval will take in its serialized + * form on disk. This is dependent on its state value. + */ + private static int computeSizeOnDisk(ITmfStateValue sv) { + /* + * Minimum size is 2x long (start and end), 1x int (attribute) and 1x + * byte (value type). + */ + int minSize = Long.BYTES + Long.BYTES + Integer.BYTES + Byte.BYTES; + + switch (sv.getType()) { + case NULL: + return minSize; + case INTEGER: + return (minSize + Integer.BYTES); + case LONG: + return (minSize + Long.BYTES); + case DOUBLE: + return (minSize + Double.BYTES); + case STRING: + String str = sv.unboxStr(); + int strLength = str.getBytes(CHARSET).length; + + if (strLength > Short.MAX_VALUE) { + throw new IllegalArgumentException("String is too long to be stored in state system: " + str); //$NON-NLS-1$ + } + + /* + * String's length + 3 (2 bytes for size, 1 byte for \0 at the end) + */ + return (minSize + strLength + 3); + case CUSTOM: + /* Length of serialized value (short) + state value */ + return (minSize + Short.BYTES + ((CustomStateValue) sv).getSerializedSize()); + default: + /* + * It's very important that we know how to write the state value in + * the file!! + */ + throw new IllegalStateException(); + } + } + + /** + * Reader object for this interval type. + */ + public static final @NonNull IHTIntervalReader<@NonNull StateSystemInterval> DESERIALISER = buffer -> { + TmfStateValue value; + + /* Read the Data Section entry */ + long intervalStart = buffer.getLong(); + long intervalEnd = buffer.getLong(); + int attribute = buffer.getInt(); + + /* Read the 'type' of the value, then react accordingly */ + byte valueType = buffer.get(); + switch (valueType) { + + case TYPE_NULL: + value = TmfStateValue.nullValue(); + break; + + case TYPE_INTEGER: + value = TmfStateValue.newValueInt(buffer.getInt()); + break; + + case TYPE_STRING: { + /* the first short = the size to read */ + int valueSize = buffer.getShort(); + + byte[] array = new byte[valueSize]; + buffer.get(array); + value = TmfStateValue.newValueString(new String(array, CHARSET)); + + /* Confirm the 0'ed byte at the end */ + byte res = buffer.get(); + if (res != 0) { + throw new RuntimeException(errMsg); + } + break; + } + + case TYPE_LONG: + /* Go read the matching entry in the Strings section of the block */ + value = TmfStateValue.newValueLong(buffer.getLong()); + break; + + case TYPE_DOUBLE: + /* Go read the matching entry in the Strings section of the block */ + value = TmfStateValue.newValueDouble(buffer.getDouble()); + break; + + case TYPE_CUSTOM: { + buffer.getShort(); + // short valueSize = buffer.getShort(); + // ISafeByteBufferReader safeBuffer = + // SafeByteBufferFactory.wrapReader(buffer, valueSize); + value = CustomStateValue.readSerializedValue(buffer); + break; + } + default: + /* Unknown data, better to not make anything up... */ + throw new RuntimeException(errMsg); + } + + try { + return new StateSystemInterval(intervalStart, intervalEnd, attribute, value); + } catch (TimeRangeException e) { + throw new RuntimeException(errMsg); + } + }; + + @Override + public void writeSegment(ISafeByteBufferWriter buffer) { + final byte byteFromType = getByteFromType(fSv.getType()); + + buffer.putLong(getStart()); + buffer.putLong(getEnd()); + buffer.putInt(fAttribute); + buffer.put(byteFromType); + + switch (byteFromType) { + case TYPE_NULL: + break; + case TYPE_INTEGER: + buffer.putInt(fSv.unboxInt()); + break; + + case TYPE_STRING: { + String string = fSv.unboxStr(); + byte[] strArray = string.getBytes(CHARSET); + + /* + * Write the Strings entry (1st byte = size, then the bytes, then + * the 0). We have checked the string length at the constructor. + */ + buffer.putShort((short) strArray.length); + buffer.put(strArray); + buffer.put((byte) 0); + break; + } + + case TYPE_LONG: + buffer.putLong(fSv.unboxLong()); + break; + + case TYPE_DOUBLE: + buffer.putDouble(fSv.unboxDouble()); + break; + + case TYPE_CUSTOM: { + int size = ((CustomStateValue) fSv).getSerializedSize(); + buffer.putShort((short) size); + // TODO: We send the full safe buffer to the custom value, its size + // should set to the necessary size only + ((CustomStateValue) fSv).serialize(buffer); + break; + } + + default: + break; + } + } + + // FIXME Remove these two duplicate methods + + @Override + public long getStartTime() { + return getStart(); + } + + @Override + public long getEndTime() { + return getEnd(); + } + + @Override + public int getAttribute() { + return fAttribute; + } + + @Override + public ITmfStateValue getStateValue() { + return fSv; + } + + @Override + public boolean intersects(long timestamp) { + /* + * Need to explicitly re-define due to conflicting methods in + * super-interfaces. + */ + return super.intersects(timestamp); + } + + @Override + public int getSizeOnDisk() { + return fSizeOnDisk; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), fAttribute, fSv); + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + + StateSystemInterval other = (StateSystemInterval) checkNotNull(obj); + return (fAttribute == other.fAttribute + && fSv.equals(other.fSv)); + } + + @Override + public String toString() { + /* Only for debug, should not be externalized */ + StringBuilder sb = new StringBuilder(); + sb.append('['); + sb.append(getStart()); + sb.append(", "); //$NON-NLS-1$ + sb.append(getEnd()); + sb.append(']'); + + sb.append(", attribute = "); //$NON-NLS-1$ + sb.append(fAttribute); + + sb.append(", value = "); //$NON-NLS-1$ + sb.append(fSv.toString()); + + return sb.toString(); + } + + /** + * Here we determine how state values "types" are written in the 8-bit field + * that indicates the value type in the file. + */ + private static byte getByteFromType(ITmfStateValue.Type type) { + switch (type) { + case NULL: + return TYPE_NULL; + case INTEGER: + return TYPE_INTEGER; + case STRING: + return TYPE_STRING; + case LONG: + return TYPE_LONG; + case DOUBLE: + return TYPE_DOUBLE; + case CUSTOM: + return TYPE_CUSTOM; + default: + /* Should not happen if the switch is fully covered */ + throw new IllegalStateException(); + } + } +} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/ThreadedHistoryTreeBackend.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/ThreadedHistoryTreeBackend.java index f55141f01c..9cbf916e8b 100644 --- a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/ThreadedHistoryTreeBackend.java +++ b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/ThreadedHistoryTreeBackend.java @@ -37,7 +37,7 @@ public final class ThreadedHistoryTreeBackend extends HistoryTreeBackend implements Runnable { private static final int CHUNK_SIZE = 127; - private final @NonNull BufferedBlockingQueue intervalQueue; + private final @NonNull BufferedBlockingQueue intervalQueue; private final @NonNull Thread shtThread; /** * The backend tracks its end time separately from the tree, to take into @@ -73,7 +73,7 @@ public final class ThreadedHistoryTreeBackend extends HistoryTreeBackend * If there was a problem opening the history file for writing */ public ThreadedHistoryTreeBackend(@NonNull String ssid, - File newStateFile, + @NonNull File newStateFile, int providerVersion, long startTime, int queueSize, @@ -110,7 +110,7 @@ public final class ThreadedHistoryTreeBackend extends HistoryTreeBackend * If there was a problem opening the history file for writing */ public ThreadedHistoryTreeBackend(@NonNull String ssid, - File newStateFile, + @NonNull File newStateFile, int providerVersion, long startTime, int queueSize) @@ -139,7 +139,7 @@ public final class ThreadedHistoryTreeBackend extends HistoryTreeBackend * underneath, we'll put them in the Queue. They will then be taken and * processed by the other thread executing the run() method. */ - HTInterval interval = new HTInterval(stateStartTime, stateEndTime, + StateSystemInterval interval = new StateSystemInterval(stateStartTime, stateEndTime, quark, (TmfStateValue) value); intervalQueue.put(interval); fEndTime = Math.max(fEndTime, stateEndTime); @@ -186,7 +186,7 @@ public final class ThreadedHistoryTreeBackend extends HistoryTreeBackend * closeTree() */ try { - HTInterval pill = new HTInterval(-1, endTime, -1, TmfStateValue.nullValue()); + StateSystemInterval pill = new StateSystemInterval(-1, endTime, -1, TmfStateValue.nullValue()); intervalQueue.put(pill); intervalQueue.flushInputBuffer(); shtThread.join(); @@ -200,10 +200,10 @@ public final class ThreadedHistoryTreeBackend extends HistoryTreeBackend @Override public void run() { try { - HTInterval currentInterval = intervalQueue.blockingPeek(); + StateSystemInterval currentInterval = intervalQueue.blockingPeek(); while (currentInterval.getStartTime() != -1) { /* Send the interval to the History Tree */ - getSHT().insertInterval(currentInterval); + getSHT().insert(currentInterval); /* Actually remove the interval from the queue */ // FIXME Replace with remove() once it is implemented. intervalQueue.take(); diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/classic/CoreNode.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/classic/CoreNode.java deleted file mode 100644 index 946c8bffc1..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/classic/CoreNode.java +++ /dev/null @@ -1,255 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010, 2016 Ericsson, École Polytechnique de Montréal, and others - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Alexandre Montplaisir - Initial API and implementation - * Florian Wininger - Add Extension and Leaf Node - *******************************************************************************/ - -package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.classic; - -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.HTConfig; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.HTNode; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.ParentNode; -import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; - -/** - * A Core node is a first-level node of a History Tree which is not a leaf node. - * - * It extends HTNode by adding support for child nodes, and also extensions. - * - * @author Alexandre Montplaisir - */ -public final class CoreNode extends ParentNode { - - /** Nb. of children this node has */ - private int nbChildren; - - /** Seq. numbers of the children nodes (size = MAX_NB_CHILDREN) */ - private int[] children; - - /** Start times of each of the children (size = MAX_NB_CHILDREN) */ - private long[] childStart; - - /** Seq number of this node's extension. -1 if none */ - private volatile int extension = -1; - - /** - * Lock used to gate the accesses to the children arrays. Meant to be a - * different lock from the one in {@link HTNode}. - */ - private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(false); - - /** - * Initial constructor. Use this to initialize a new EMPTY node. - * - * @param config - * Configuration of the History Tree - * @param seqNumber - * The (unique) sequence number assigned to this particular node - * @param parentSeqNumber - * The sequence number of this node's parent node - * @param start - * The earliest timestamp stored in this node - */ - public CoreNode(HTConfig config, int seqNumber, int parentSeqNumber, - long start) { - super(config, seqNumber, parentSeqNumber, start); - this.nbChildren = 0; - int size = config.getMaxChildren(); - - /* - * We instantiate the two following arrays at full size right away, - * since we want to reserve that space in the node's header. - * "this.nbChildren" will tell us how many relevant entries there are in - * those tables. - */ - this.children = new int[size]; - this.childStart = new long[size]; - } - - @Override - protected void readSpecificHeader(ByteBuffer buffer) { - int size = getConfig().getMaxChildren(); - - extension = buffer.getInt(); - nbChildren = buffer.getInt(); - - children = new int[size]; - for (int i = 0; i < nbChildren; i++) { - children[i] = buffer.getInt(); - } - for (int i = nbChildren; i < size; i++) { - buffer.getInt(); - } - - this.childStart = new long[size]; - for (int i = 0; i < nbChildren; i++) { - childStart[i] = buffer.getLong(); - } - for (int i = nbChildren; i < size; i++) { - buffer.getLong(); - } - } - - @Override - protected void writeSpecificHeader(ByteBuffer buffer) { - int size = getConfig().getMaxChildren(); - - buffer.putInt(extension); - buffer.putInt(nbChildren); - - /* Write the "children's seq number" array */ - for (int i = 0; i < nbChildren; i++) { - buffer.putInt(children[i]); - } - for (int i = nbChildren; i < size; i++) { - buffer.putInt(0); - } - - /* Write the "children's start times" array */ - for (int i = 0; i < nbChildren; i++) { - buffer.putLong(childStart[i]); - } - for (int i = nbChildren; i < size; i++) { - buffer.putLong(0); - } - } - - @Override - public int getNbChildren() { - rwl.readLock().lock(); - try { - return nbChildren; - } finally { - rwl.readLock().unlock(); - } - } - - @Override - public int getChild(int index) { - rwl.readLock().lock(); - try { - return children[index]; - } finally { - rwl.readLock().unlock(); - } - } - - @Override - public int getLatestChild() { - rwl.readLock().lock(); - try { - return children[nbChildren - 1]; - } finally { - rwl.readLock().unlock(); - } - } - - @Override - public long getChildStart(int index) { - rwl.readLock().lock(); - try { - return childStart[index]; - } finally { - rwl.readLock().unlock(); - } - } - - /** - * Get the sequence number of the extension to this node (if there is one). - * - * @return The sequence number of the extended node. '-1' is returned if - * there is no extension node. - */ - public int getExtensionSequenceNumber() { - rwl.readLock().lock(); - try { - return extension; - } finally { - rwl.readLock().unlock(); - } - } - - @Override - public void linkNewChild(HTNode childNode) { - rwl.writeLock().lock(); - try { - if (nbChildren >= getConfig().getMaxChildren()) { - throw new IllegalStateException("Asked to link another child but parent already has maximum number of children"); //$NON-NLS-1$ - } - - children[nbChildren] = childNode.getSequenceNumber(); - childStart[nbChildren] = childNode.getNodeStart(); - nbChildren++; - - } finally { - rwl.writeLock().unlock(); - } - } - - @Override - public Collection selectNextChildren(long t) throws TimeRangeException { - if (t < getNodeStart() || (isOnDisk() && t > getNodeEnd())) { - throw new TimeRangeException("Requesting children outside the node's range: " + t); //$NON-NLS-1$ - } - rwl.readLock().lock(); - try { - int potentialNextSeqNb = -1; - for (int i = 0; i < nbChildren; i++) { - if (t >= childStart[i]) { - potentialNextSeqNb = children[i]; - } else { - break; - } - } - - if (potentialNextSeqNb == -1) { - throw new IllegalStateException("No next child node found"); //$NON-NLS-1$ - } - return Collections.singleton(potentialNextSeqNb); - } finally { - rwl.readLock().unlock(); - } - } - - @Override - public NodeType getNodeType() { - return NodeType.CORE; - } - - @Override - protected int getSpecificHeaderSize() { - int maxChildren = getConfig().getMaxChildren(); - int specificSize = - Integer.BYTES /* 1x int (extension node) */ - + Integer.BYTES /* 1x int (nbChildren) */ - - /* MAX_NB * int ('children' table) */ - + Integer.BYTES * maxChildren - - /* MAX_NB * Timevalue ('childStart' table) */ - + Long.BYTES * maxChildren; - - return specificSize; - } - - @Override - public String toStringSpecific() { - /* Only used for debugging, shouldn't be externalized */ - return String.format("Core Node, %d children %s", //$NON-NLS-1$ - nbChildren, Arrays.toString(Arrays.copyOf(children, nbChildren))); - } - -} diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/classic/HistoryTreeClassic.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/classic/HistoryTreeClassic.java deleted file mode 100644 index a59bfe3f9e..0000000000 --- a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/internal/statesystem/core/backend/historytree/classic/HistoryTreeClassic.java +++ /dev/null @@ -1,639 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010, 2016 Ericsson, École Polytechnique de Montréal, and others - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Alexandre Montplaisir - Initial API and implementation - * Florian Wininger - Add Extension and Leaf Node - * Patrick Tasse - Add message to exceptions - *******************************************************************************/ - -package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.classic; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.ClosedChannelException; -import java.nio.channels.FileChannel; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.eclipse.jdt.annotation.NonNull; -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.HT_IO; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.IHistoryTree; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.LeafNode; -import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.ParentNode; -import org.eclipse.tracecompass.statesystem.core.ITmfStateSystemBuilder; -import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; - -/** - * Meta-container for the History Tree. This structure contains all the - * high-level data relevant to the tree. - * - * @author Alexandre Montplaisir - */ -public class HistoryTreeClassic implements IHistoryTree { - - /** - * The magic number for this file format. - */ - public static final int HISTORY_FILE_MAGIC_NUMBER = 0x05FFA900; - - /** File format version. Increment when breaking compatibility. */ - private static final int FILE_VERSION = 7; - - private static final IHTNodeFactory CLASSIC_NODE_FACTORY = new IHTNodeFactory() { - - @Override - public HTNode createCoreNode(HTConfig config, int seqNumber, int parentSeqNumber, long start) { - return new CoreNode(config, seqNumber, parentSeqNumber, start); - } - - @Override - public HTNode createLeafNode(HTConfig config, int seqNumber, int parentSeqNumber, long start) { - return new LeafNode(config, seqNumber, parentSeqNumber, start); - } - - }; - - // ------------------------------------------------------------------------ - // Tree-specific configuration - // ------------------------------------------------------------------------ - - /** Container for all the configuration constants */ - private final HTConfig fConfig; - - /** Reader/writer object */ - private final @NonNull HT_IO fTreeIO; - - // ------------------------------------------------------------------------ - // Variable Fields (will change throughout the existence of the SHT) - // ------------------------------------------------------------------------ - - /** Latest timestamp found in the tree (at any given moment) */ - private long fTreeEnd; - - /** The total number of nodes that exists in this tree */ - private int fNodeCount; - - /** "Cache" to keep the active nodes in memory */ - private final @NonNull List<@NonNull HTNode> fLatestBranch; - - // ------------------------------------------------------------------------ - // Constructors/"Destructors" - // ------------------------------------------------------------------------ - - /** - * Create a new State History from scratch, using a {@link HTConfig} object - * for configuration. - * - * @param conf - * The config to use for this History Tree. - * @throws IOException - * If an error happens trying to open/write to the file - * specified in the config - */ - public HistoryTreeClassic(HTConfig conf) throws IOException { - /* - * Simple check to make sure we have enough place in the 0th block for - * the tree configuration - */ - if (conf.getBlockSize() < TREE_HEADER_SIZE) { - throw new IllegalArgumentException(); - } - - fConfig = conf; - fTreeEnd = conf.getTreeStart(); - fNodeCount = 0; - fLatestBranch = Collections.synchronizedList(new ArrayList<>()); - - /* Prepare the IO object */ - fTreeIO = new HT_IO(fConfig, true, CLASSIC_NODE_FACTORY); - - /* Add the first node to the tree */ - LeafNode firstNode = initNewLeafNode(-1, conf.getTreeStart()); - fLatestBranch.add(firstNode); - } - - /** - * "Reader" constructor : instantiate a SHTree from an existing tree file on - * disk - * - * @param existingStateFile - * Path/filename of the history-file we are to open - * @param expProviderVersion - * The expected version of the state provider - * @throws IOException - * If an error happens reading the file - */ - public HistoryTreeClassic(File existingStateFile, int expProviderVersion) throws IOException { - /* - * Open the file ourselves, get the tree header information we need, - * then pass on the descriptor to the TreeIO object. - */ - int rootNodeSeqNb, res; - int bs, maxc; - long startTime; - - /* Java I/O mumbo jumbo... */ - if (!existingStateFile.exists()) { - throw new IOException("Selected state file does not exist"); //$NON-NLS-1$ - } - if (existingStateFile.length() <= 0) { - throw new IOException("Empty target file"); //$NON-NLS-1$ - } - - try (FileInputStream fis = new FileInputStream(existingStateFile); - FileChannel fc = fis.getChannel();) { - - ByteBuffer buffer = ByteBuffer.allocate(TREE_HEADER_SIZE); - buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.clear(); - - res = fc.read(buffer); - if (res != TREE_HEADER_SIZE) { - throw new IOException("Invalid header size"); //$NON-NLS-1$ - } - - buffer.flip(); - - /* - * Check the magic number to make sure we're opening the right type - * of file - */ - res = buffer.getInt(); - if (res != HISTORY_FILE_MAGIC_NUMBER) { - throw new IOException("Wrong magic number"); //$NON-NLS-1$ - } - - res = buffer.getInt(); /* File format version number */ - if (res != FILE_VERSION) { - throw new IOException("Mismatching History Tree file format versions"); //$NON-NLS-1$ - } - - res = buffer.getInt(); /* Event handler's version number */ - if (res != expProviderVersion && - expProviderVersion != ITmfStateSystemBuilder.IGNORE_PROVIDER_VERSION) { - /* - * The existing history was built using an event handler that - * doesn't match the current one in the framework. - * - * Information could be all wrong. Instead of keeping an - * incorrect history file, a rebuild is done. - */ - throw new IOException("Mismatching event handler versions"); //$NON-NLS-1$ - } - - bs = buffer.getInt(); /* Block Size */ - maxc = buffer.getInt(); /* Max nb of children per node */ - - fNodeCount = buffer.getInt(); - rootNodeSeqNb = buffer.getInt(); - startTime = buffer.getLong(); - - fConfig = new HTConfig(existingStateFile, bs, maxc, expProviderVersion, startTime); - } - - /* - * FIXME We close fis here and the TreeIO will then reopen the same - * file, not extremely elegant. But how to pass the information here to - * the SHT otherwise? - */ - fTreeIO = new HT_IO(fConfig, false, CLASSIC_NODE_FACTORY); - - fLatestBranch = buildLatestBranch(rootNodeSeqNb); - fTreeEnd = getRootNode().getNodeEnd(); - - /* - * Make sure the history start time we read previously is consistent - * with was is actually in the root node. - */ - if (startTime != getRootNode().getNodeStart()) { - throw new IOException("Inconsistent start times in the" + //$NON-NLS-1$ - "history file, it might be corrupted."); //$NON-NLS-1$ - } - } - - /** - * Rebuild the latestBranch "cache" object by reading the nodes from disk - * (When we are opening an existing file on disk and want to append to it, - * for example). - * - * @param rootNodeSeqNb - * The sequence number of the root node, so we know where to - * start - * @throws ClosedChannelException - */ - private @NonNull List<@NonNull HTNode> buildLatestBranch(int rootNodeSeqNb) throws ClosedChannelException { - List<@NonNull HTNode> list = new ArrayList<>(); - - HTNode nextChildNode = fTreeIO.readNode(rootNodeSeqNb); - list.add(nextChildNode); - - /* Follow the last branch up to the leaf */ - while (nextChildNode.getNodeType() == HTNode.NodeType.CORE) { - nextChildNode = fTreeIO.readNode(((CoreNode) nextChildNode).getLatestChild()); - list.add(nextChildNode); - } - return Collections.synchronizedList(list); - } - - @Override - public void closeTree(long requestedEndTime) { - /* This is an important operation, queries can wait */ - synchronized (fLatestBranch) { - /* - * Work-around the "empty branches" that get created when the root - * node becomes full. Overwrite the tree's end time with the - * original wanted end-time, to ensure no queries are sent into - * those empty nodes. - * - * This won't be needed once extended nodes are implemented. - */ - fTreeEnd = requestedEndTime; - - /* Close off the latest branch of the tree */ - for (int i = 0; i < fLatestBranch.size(); i++) { - fLatestBranch.get(i).closeThisNode(fTreeEnd); - fTreeIO.writeNode(fLatestBranch.get(i)); - } - - try (FileChannel fc = fTreeIO.getFcOut();) { - ByteBuffer buffer = ByteBuffer.allocate(TREE_HEADER_SIZE); - buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.clear(); - - /* Save the config of the tree to the header of the file */ - fc.position(0); - - buffer.putInt(HISTORY_FILE_MAGIC_NUMBER); - - buffer.putInt(FILE_VERSION); - buffer.putInt(fConfig.getProviderVersion()); - - buffer.putInt(fConfig.getBlockSize()); - buffer.putInt(fConfig.getMaxChildren()); - - buffer.putInt(fNodeCount); - - /* root node seq. nb */ - buffer.putInt(fLatestBranch.get(0).getSequenceNumber()); - - /* start time of this history */ - buffer.putLong(fLatestBranch.get(0).getNodeStart()); - - buffer.flip(); - int res = fc.write(buffer); - assert (res <= TREE_HEADER_SIZE); - /* done writing the file header */ - - } catch (IOException e) { - /* - * If we were able to write so far, there should not be any - * problem at this point... - */ - throw new RuntimeException("State system write error"); //$NON-NLS-1$ - } - } - } - - // ------------------------------------------------------------------------ - // Accessors - // ------------------------------------------------------------------------ - - @Override - public long getTreeStart() { - return fConfig.getTreeStart(); - } - - @Override - public long getTreeEnd() { - return fTreeEnd; - } - - @Override - public int getNodeCount() { - return fNodeCount; - } - - @Override - public HTNode getRootNode() { - return fLatestBranch.get(0); - } - - /** - * Return the latest branch of the tree. That branch is immutable. Used for - * unit testing and debugging. - * - * @return The immutable latest branch - */ - @VisibleForTesting - protected List<@NonNull HTNode> getLatestBranch() { - return ImmutableList.copyOf(fLatestBranch); - } - - /** - * Read a node at sequence number - * - * @param seqNum - * The sequence number of the node to read - * @return The HTNode object - * @throws ClosedChannelException - * Exception thrown when reading the node - */ - @VisibleForTesting - protected @NonNull HTNode getNode(int seqNum) throws ClosedChannelException { - // First, check in the latest branch if the node is there - for (HTNode node : fLatestBranch) { - if (node.getSequenceNumber() == seqNum) { - return node; - } - } - return fTreeIO.readNode(seqNum); - } - - /** - * Retrieve the TreeIO object. Should only be used for testing. - * - * @return The TreeIO - */ - @VisibleForTesting - protected @NonNull HT_IO getTreeIO() { - return fTreeIO; - } - - // ------------------------------------------------------------------------ - // HT_IO interface - // ------------------------------------------------------------------------ - - @Override - public FileInputStream supplyATReader() { - return fTreeIO.supplyATReader(getNodeCount()); - } - - @Override - public File supplyATWriterFile() { - return fConfig.getStateFile(); - } - - @Override - public long supplyATWriterFilePos() { - return IHistoryTree.TREE_HEADER_SIZE - + ((long) getNodeCount() * fConfig.getBlockSize()); - } - - @Override - public HTNode readNode(int seqNumber) throws ClosedChannelException { - /* Try to read the node from memory */ - synchronized (fLatestBranch) { - for (HTNode node : fLatestBranch) { - if (node.getSequenceNumber() == seqNumber) { - return node; - } - } - } - - /* Read the node from disk */ - return fTreeIO.readNode(seqNumber); - } - - @Override - public void writeNode(HTNode node) { - fTreeIO.writeNode(node); - } - - @Override - public void closeFile() { - fTreeIO.closeFile(); - } - - @Override - public void deleteFile() { - fTreeIO.deleteFile(); - } - - // ------------------------------------------------------------------------ - // Operations - // ------------------------------------------------------------------------ - - @Override - public void insertInterval(HTInterval interval) throws TimeRangeException { - if (interval.getStartTime() < fConfig.getTreeStart()) { - throw new TimeRangeException("Interval Start:" + interval.getStartTime() + ", Config Start:" + fConfig.getTreeStart()); //$NON-NLS-1$ //$NON-NLS-2$ - } - tryInsertAtNode(interval, fLatestBranch.size() - 1); - } - - /** - * Inner method to find in which node we should add the interval. - * - * @param interval - * The interval to add to the tree - * @param indexOfNode - * The index *in the latestBranch* where we are trying the - * insertion - */ - private void tryInsertAtNode(HTInterval interval, int indexOfNode) { - HTNode targetNode = fLatestBranch.get(indexOfNode); - - /* Verify if there is enough room in this node to store this interval */ - if (interval.getSizeOnDisk() > targetNode.getNodeFreeSpace()) { - /* Nope, not enough room. Insert in a new sibling instead. */ - addSiblingNode(indexOfNode); - tryInsertAtNode(interval, fLatestBranch.size() - 1); - return; - } - - /* Make sure the interval time range fits this node */ - if (interval.getStartTime() < targetNode.getNodeStart()) { - /* - * No, this interval starts before the startTime of this node. We - * need to check recursively in parents if it can fit. - */ - tryInsertAtNode(interval, indexOfNode - 1); - return; - } - - /* - * Ok, there is room, and the interval fits in this time slot. Let's add - * it. - */ - targetNode.addInterval(interval); - - /* Update treeEnd if needed */ - if (interval.getEndTime() > fTreeEnd) { - fTreeEnd = interval.getEndTime(); - } - } - - /** - * Method to add a sibling to any node in the latest branch. This will add - * children back down to the leaf level, if needed. - * - * @param indexOfNode - * The index in latestBranch where we start adding - */ - private void addSiblingNode(int indexOfNode) { - synchronized (fLatestBranch) { - final long splitTime = fTreeEnd; - - if (indexOfNode >= fLatestBranch.size()) { - /* - * We need to make sure (indexOfNode - 1) doesn't get the last - * node in the branch, because that one is a Leaf Node. - */ - throw new IllegalStateException(); - } - - /* Check if we need to add a new root node */ - if (indexOfNode == 0) { - addNewRootNode(); - return; - } - - /* Check if we can indeed add a child to the target parent */ - if (((ParentNode) fLatestBranch.get(indexOfNode - 1)).getNbChildren() == fConfig.getMaxChildren()) { - /* If not, add a branch starting one level higher instead */ - addSiblingNode(indexOfNode - 1); - return; - } - - /* Split off the new branch from the old one */ - for (int i = indexOfNode; i < fLatestBranch.size(); i++) { - fLatestBranch.get(i).closeThisNode(splitTime); - fTreeIO.writeNode(fLatestBranch.get(i)); - - ParentNode prevNode = (ParentNode) fLatestBranch.get(i - 1); - HTNode newNode; - - switch (fLatestBranch.get(i).getNodeType()) { - case CORE: - newNode = initNewCoreNode(prevNode.getSequenceNumber(), splitTime + 1); - break; - case LEAF: - newNode = initNewLeafNode(prevNode.getSequenceNumber(), splitTime + 1); - break; - default: - throw new IllegalStateException(); - } - - prevNode.linkNewChild(newNode); - fLatestBranch.set(i, newNode); - } - } - } - - /** - * Similar to the previous method, except here we rebuild a completely new - * latestBranch - */ - private void addNewRootNode() { - final long splitTime = fTreeEnd; - - HTNode oldRootNode = fLatestBranch.get(0); - ParentNode newRootNode = initNewCoreNode(-1, fConfig.getTreeStart()); - - /* Tell the old root node that it isn't root anymore */ - oldRootNode.setParentSequenceNumber(newRootNode.getSequenceNumber()); - - /* Close off the whole current latestBranch */ - - for (int i = 0; i < fLatestBranch.size(); i++) { - fLatestBranch.get(i).closeThisNode(splitTime); - fTreeIO.writeNode(fLatestBranch.get(i)); - } - - /* Link the new root to its first child (the previous root node) */ - newRootNode.linkNewChild(oldRootNode); - - /* Rebuild a new latestBranch */ - int depth = fLatestBranch.size(); - fLatestBranch.clear(); - fLatestBranch.add(newRootNode); - - // Create new coreNode - for (int i = 1; i < depth; i++) { - ParentNode prevNode = (ParentNode) fLatestBranch.get(i - 1); - ParentNode newNode = initNewCoreNode(prevNode.getSequenceNumber(), - splitTime + 1); - prevNode.linkNewChild(newNode); - fLatestBranch.add(newNode); - } - - // Create the new leafNode - ParentNode prevNode = (ParentNode) fLatestBranch.get(depth - 1); - LeafNode newNode = initNewLeafNode(prevNode.getSequenceNumber(), splitTime + 1); - prevNode.linkNewChild(newNode); - fLatestBranch.add(newNode); - } - - /** - * Add a new empty core node to the tree. - * - * @param parentSeqNumber - * Sequence number of this node's parent - * @param startTime - * Start time of the new node - * @return The newly created node - */ - private @NonNull ParentNode initNewCoreNode(int parentSeqNumber, long startTime) { - ParentNode newNode = new CoreNode(fConfig, fNodeCount, parentSeqNumber, - startTime); - fNodeCount++; - return newNode; - } - - /** - * Add a new empty leaf node to the tree. - * - * @param parentSeqNumber - * Sequence number of this node's parent - * @param startTime - * Start time of the new node - * @return The newly created node - */ - private @NonNull LeafNode initNewLeafNode(int parentSeqNumber, long startTime) { - LeafNode newNode = new LeafNode(fConfig, fNodeCount, parentSeqNumber, - startTime); - fNodeCount++; - return newNode; - } - - @Override - public long getFileSize() { - return fConfig.getStateFile().length(); - } - - // ------------------------------------------------------------------------ - // Test/debugging methods - // ------------------------------------------------------------------------ - - /* Only used for debugging, shouldn't be externalized */ - @SuppressWarnings("nls") - @Override - public String toString() { - return "Information on the current tree:\n\n" + "Blocksize: " - + fConfig.getBlockSize() + "\n" + "Max nb. of children per node: " - + fConfig.getMaxChildren() + "\n" + "Number of nodes: " + fNodeCount - + "\n" + "Depth of the tree: " + fLatestBranch.size() + "\n" - + "Size of the treefile: " + getFileSize() + "\n" - + "Root node has sequence number: " - + fLatestBranch.get(0).getSequenceNumber() + "\n" - + "'Latest leaf' has sequence number: " - + fLatestBranch.get(fLatestBranch.size() - 1).getSequenceNumber(); - } - -}