From: Matthew Khouzam Date: Tue, 17 May 2016 19:51:12 +0000 (-0400) Subject: timing.core: add ArrayListStore implementing ISegmentStore X-Git-Url: http://git.efficios.com/?a=commitdiff_plain;h=3dde9149d8fa9f132e62550069acda8107c8bd22;p=deliverable%2Ftracecompass.git timing.core: add ArrayListStore implementing ISegmentStore This store is made for a corner case where we want a slightly more memory efficient datastore. This is used by the timing analysis to save a bit of memory at the expense of intersection times. This partially addresses bug 489217 Change-Id: I364d498526ac46d357d9788714cea25816efd536 Signed-off-by: Matthew Khouzam Reviewed-on: https://git.eclipse.org/r/72968 Tested-by: Bernd Hufmann Reviewed-by: Hudson CI Reviewed-by: Bernd Hufmann --- diff --git a/analysis/org.eclipse.tracecompass.analysis.timing.core.tests/src/org/eclipse/tracecompass/analysis/timing/core/tests/store/ArrayListStoreTest.java b/analysis/org.eclipse.tracecompass.analysis.timing.core.tests/src/org/eclipse/tracecompass/analysis/timing/core/tests/store/ArrayListStoreTest.java new file mode 100644 index 0000000000..0d90553e06 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.timing.core.tests/src/org/eclipse/tracecompass/analysis/timing/core/tests/store/ArrayListStoreTest.java @@ -0,0 +1,294 @@ +/******************************************************************************* + * Copyright (c) 2016 Ericsson + * + * 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.analysis.timing.core.tests.store; + +import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.tracecompass.internal.analysis.timing.core.store.ArrayListStore; +import org.eclipse.tracecompass.segmentstore.core.BasicSegment; +import org.eclipse.tracecompass.segmentstore.core.ISegment; +import org.eclipse.tracecompass.segmentstore.core.ISegmentStore; +import org.eclipse.tracecompass.segmentstore.core.treemap.TreeMapStore; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +/** + * Unit tests for intersecting elements in an ArrayListStore + * + * Originally the TreeMapStoreTest, copied for this internal implementation. The + * test was barely changed as it tests the interface and not the internals. + * + * @author France Lapointe Nguyen + * @author Matthew Khouzam + */ +public class ArrayListStoreTest { + + private ISegmentStore<@NonNull ISegment> fSegmentStore; + + private static final @NonNull ISegment SEGMENT_2_6 = new BasicSegment(2, 6); + private static final @NonNull ISegment SEGMENT_4_6 = new BasicSegment(4, 6); + private static final @NonNull ISegment SEGMENT_4_8 = new BasicSegment(4, 8); + private static final @NonNull ISegment SEGMENT_6_8 = new BasicSegment(6, 8); + private static final @NonNull ISegment SEGMENT_10_14 = new BasicSegment(10, 14); + + private static final List SEGMENTS = ImmutableList.of(SEGMENT_2_6, SEGMENT_4_6, SEGMENT_4_8, SEGMENT_6_8, SEGMENT_10_14); + private static final List REVERSE_SEGMENTS = Lists.reverse(SEGMENTS); + + /** + * Initialize data (test vector) that will be tested + */ + @Before + public void setup() { + fSegmentStore = new ArrayListStore<>(); + for (ISegment segment : SEGMENTS) { + fSegmentStore.add(checkNotNull(segment)); + } + } + + /** + * Dispose of the segment store + */ + @After + public void teardown() { + fSegmentStore.dispose(); + } + + /** + * Testing method size() + */ + @Test + public void testSize() { + assertEquals(SEGMENTS.size(), fSegmentStore.size()); + } + + /** + * Test the contains() method. + */ + @Test + public void testContains() { + ISegment otherSegment = new BasicSegment(0, 20); + + assertTrue(fSegmentStore.contains(SEGMENT_2_6)); + assertTrue(fSegmentStore.contains(SEGMENT_4_8)); + assertFalse(fSegmentStore.contains(otherSegment)); + } + + /** + * Test the toArray() method. + */ + @Test + public void testToObjectArray() { + Object[] array = fSegmentStore.toArray(); + + assertEquals(SEGMENTS.size(), array.length); + assertTrue(Arrays.asList(array).containsAll(SEGMENTS)); + } + + /** + * Test the toArray(T[]) method. + */ + @Test + public void testToSpecificArray() { + ISegment[] array = fSegmentStore.toArray(new ISegment[0]); + + assertEquals(SEGMENTS.size(), array.length); + assertTrue(Arrays.asList(array).containsAll(SEGMENTS)); + } + + /** + * Test the toArray(T[]) method with a subtype of ISegment. + */ + @Test + public void testToSpecifyArraySubtype() { + TreeMapStore<@NonNull BasicSegment> tms2 = new TreeMapStore<>(); + BasicSegment otherSegment = new BasicSegment(2, 6); + tms2.add(otherSegment); + BasicSegment[] array = tms2.toArray(new BasicSegment[0]); + + assertEquals(1, array.length); + assertTrue(Arrays.asList(array).contains(otherSegment)); + + tms2.dispose(); + } + + /** + * Test the iteration order of the complete segment store. + */ + @Test + public void testIterationOrder() { + int i = 0; + for (ISegment segment : fSegmentStore) { + assertEquals(SEGMENTS.get(i++), segment); + } + } + + /** + * Test the iteration order when the elements are not inserted in sorted + * order. + */ + @Test + public void testIterationOrderNonSortedInsertion() { + /* Prepare the segment store, we don't use the 'fixture' in this test */ + TreeMapStore<@NonNull ISegment> store = new TreeMapStore<>(); + for (ISegment segment : REVERSE_SEGMENTS) { + store.add(checkNotNull(segment)); + } + + /* + * Test each element one by one, the iteration order should follow the + * start times, not the insertion order. + */ + int i = 0; + for (ISegment segment : store) { + assertEquals(SEGMENTS.get(i++), segment); + } + + /* Manually dispose our own store */ + store.dispose(); + } + + /** + * Testing method getIntersectingElements(long start, long end) + */ + @Test + public void testGetIntersectingElementsRange() { + + Iterable intersectingElements; + + /* + * Range that does not include any segment + */ + intersectingElements = fSegmentStore.getIntersectingElements(16, 20); + assertEquals(0, Iterables.size(intersectingElements)); + + /* + * Range start time : Before first segment start time Range end time : + * After last segment end time + */ + intersectingElements = fSegmentStore.getIntersectingElements(1, 15); + assertEquals(5, Iterables.size(intersectingElements)); + + /* + * Range start time : On first segment start time Range end time : On + * last segment end time + */ + intersectingElements = fSegmentStore.getIntersectingElements(2, 14); + assertEquals(5, Iterables.size(intersectingElements)); + + /* + * Range start time : After one segment start time Range end time : + * Before one segment end time + */ + intersectingElements = fSegmentStore.getIntersectingElements(11, 13); + assertEquals(1, Iterables.size(intersectingElements)); + assertEquals(SEGMENT_10_14, Iterables.getOnlyElement(intersectingElements)); + + /* + * Range start time : On one segment start time Range end time : On one + * segment end time + */ + intersectingElements = fSegmentStore.getIntersectingElements(10, 14); + assertEquals(1, Iterables.size(intersectingElements)); + assertEquals(SEGMENT_10_14, Iterables.getOnlyElement(intersectingElements)); + + /* + * Range start time : On last segment end time Range end time : After + * last segment end time + */ + intersectingElements = fSegmentStore.getIntersectingElements(14, 18); + assertEquals(1, Iterables.size(intersectingElements)); + assertEquals(SEGMENT_10_14, Iterables.getOnlyElement(intersectingElements)); + + /* + * Range start time : Before first segment start time Range end time : + * On first segment start time + */ + intersectingElements = fSegmentStore.getIntersectingElements(1, 2); + assertEquals(1, Iterables.size(intersectingElements)); + assertEquals(SEGMENT_2_6, Iterables.getOnlyElement(intersectingElements)); + } + + /** + * Testing method getIntersectingElements(long start, long end) + */ + @Test + public void testGetIntersectingElementsTime() { + + Iterable intersectingElements; + + /* + * Time between segment start time and end time + */ + intersectingElements = fSegmentStore.getIntersectingElements(3); + assertEquals(1, Iterables.size(intersectingElements)); + assertEquals(SEGMENT_2_6, Iterables.getOnlyElement(intersectingElements)); + + /* + * Time on segment start time + */ + intersectingElements = fSegmentStore.getIntersectingElements(2); + assertEquals(1, Iterables.size(intersectingElements)); + assertEquals(SEGMENT_2_6, Iterables.getOnlyElement(intersectingElements)); + + /* + * Time on segment end time + */ + intersectingElements = fSegmentStore.getIntersectingElements(14); + assertEquals(1, Iterables.size(intersectingElements)); + assertEquals(SEGMENT_10_14, Iterables.getOnlyElement(intersectingElements)); + + /* + * Time overlapping many segments + */ + intersectingElements = fSegmentStore.getIntersectingElements(6); + assertEquals(4, Iterables.size(intersectingElements)); + + /* + * Time between segments + */ + intersectingElements = fSegmentStore.getIntersectingElements(9); + assertEquals(0, Iterables.size(intersectingElements)); + + /* + * Time before all segment start time + */ + intersectingElements = fSegmentStore.getIntersectingElements(1); + assertEquals(0, Iterables.size(intersectingElements)); + + /* + * Time after all segment end time + */ + intersectingElements = fSegmentStore.getIntersectingElements(15); + assertEquals(0, Iterables.size(intersectingElements)); + } + + /** + * Testing method getIntersectingElements(long start, long end) + */ + @Test + public void testDispose() { + TreeMapStore<@NonNull ISegment> store = new TreeMapStore<>(); + store.add(SEGMENT_2_6); + store.dispose(); + assertEquals(0, store.size()); + } +} \ No newline at end of file diff --git a/analysis/org.eclipse.tracecompass.analysis.timing.core/META-INF/MANIFEST.MF b/analysis/org.eclipse.tracecompass.analysis.timing.core/META-INF/MANIFEST.MF index 97d6201df7..595fc4ab6d 100644 --- a/analysis/org.eclipse.tracecompass.analysis.timing.core/META-INF/MANIFEST.MF +++ b/analysis/org.eclipse.tracecompass.analysis.timing.core/META-INF/MANIFEST.MF @@ -16,6 +16,7 @@ Require-Bundle: org.eclipse.ui, org.eclipse.tracecompass.segmentstore.core Export-Package: org.eclipse.tracecompass.analysis.timing.core.segmentstore, org.eclipse.tracecompass.analysis.timing.core.segmentstore.statistics, - org.eclipse.tracecompass.internal.analysis.timing.core + org.eclipse.tracecompass.internal.analysis.timing.core, + org.eclipse.tracecompass.internal.analysis.timing.core.store;x-friends:="org.eclipse.tracecompass.analysis.timing.core.tests" Import-Package: com.google.common.collect, com.google.common.hash diff --git a/analysis/org.eclipse.tracecompass.analysis.timing.core/src/org/eclipse/tracecompass/analysis/timing/core/segmentstore/AbstractSegmentStoreAnalysisModule.java b/analysis/org.eclipse.tracecompass.analysis.timing.core/src/org/eclipse/tracecompass/analysis/timing/core/segmentstore/AbstractSegmentStoreAnalysisModule.java index c17c06a412..0518880fdc 100644 --- a/analysis/org.eclipse.tracecompass.analysis.timing.core/src/org/eclipse/tracecompass/analysis/timing/core/segmentstore/AbstractSegmentStoreAnalysisModule.java +++ b/analysis/org.eclipse.tracecompass.analysis.timing.core/src/org/eclipse/tracecompass/analysis/timing/core/segmentstore/AbstractSegmentStoreAnalysisModule.java @@ -23,9 +23,9 @@ import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.internal.analysis.timing.core.store.ArrayListStore; import org.eclipse.tracecompass.segmentstore.core.ISegment; import org.eclipse.tracecompass.segmentstore.core.ISegmentStore; -import org.eclipse.tracecompass.segmentstore.core.treemap.TreeMapStore; import org.eclipse.tracecompass.tmf.core.analysis.TmfAbstractAnalysisModule; import org.eclipse.tracecompass.tmf.core.exceptions.TmfAnalysisException; import org.eclipse.tracecompass.tmf.core.segment.ISegmentAspect; @@ -146,7 +146,7 @@ public abstract class AbstractSegmentStoreAnalysisModule extends TmfAbstractAnal /* Attempt to read the existing file */ try (ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(file))) { Object[] segmentArray = readObject(ois); - final ISegmentStore store = new TreeMapStore<>(); + final ISegmentStore store = new ArrayListStore<>(); for (Object element : segmentArray) { if (element instanceof ISegment) { ISegment segment = (ISegment) element; @@ -169,7 +169,7 @@ public abstract class AbstractSegmentStoreAnalysisModule extends TmfAbstractAnal } } - ISegmentStore segmentStore = new TreeMapStore<>(); + ISegmentStore segmentStore = new ArrayListStore<>(); boolean completed = buildAnalysisSegments(segmentStore, monitor); if (!completed) { return false; diff --git a/analysis/org.eclipse.tracecompass.analysis.timing.core/src/org/eclipse/tracecompass/internal/analysis/timing/core/store/ArrayListStore.java b/analysis/org.eclipse.tracecompass.analysis.timing.core/src/org/eclipse/tracecompass/internal/analysis/timing/core/store/ArrayListStore.java new file mode 100644 index 0000000000..0e1f494b30 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.timing.core/src/org/eclipse/tracecompass/internal/analysis/timing/core/store/ArrayListStore.java @@ -0,0 +1,255 @@ +/******************************************************************************* + * Copyright (c) 2016 Ericsson + * + * 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.analysis.timing.core.store; + +import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.segmentstore.core.ISegment; +import org.eclipse.tracecompass.segmentstore.core.ISegmentStore; + +import com.google.common.collect.ImmutableList; + +/** + * Implementation of an {@link ISegmentStore} using one in-memory + * {@link ArrayList}. This relatively simple implementation holds everything in + * memory, and as such cannot contain too much data. + * + * The ArrayListStore itself is {@link Iterable}, and its iteration order will + * be by ascending order of start times. For segments with identical start + * times, the secondary comparator will be the end time. If even those are + * equal, it will defer to the segments' natural ordering ( + * {@link ISegment#compareTo}). + * + * The store's tree maps will not accept duplicate key-value pairs, which means + * that if you want several segments with the same start and end times, make + * sure their compareTo() differentiates them. + * + * Removal operations are not supported. + * + * @param + * The type of segment held in this store + * + * @author Matthew Khouzam + */ +public class ArrayListStore<@NonNull E extends ISegment> implements ISegmentStore { + + private final Comparator COMPARATOR = (o1, o2) -> { + int ret = Long.compare(o1.getStart(), o2.getStart()); + if (ret == 0) { + return Long.compare(o1.getEnd(), o2.getEnd()); + } + return ret; + }; + + private final ReadWriteLock fLock = new ReentrantReadWriteLock(false); + + private final List fStore = new ArrayList<>(); + + private @Nullable transient Iterable fLastSnapshot = null; + + /** + * Constructor + */ + public ArrayListStore() { + /* + * For the start times index, the "key comparator" will compare the + * start times as longs directly. This is the primary comparator for its + * tree map. + * + * The secondary "value" comparator will check the end times first. + * + * The same is done for the end times index, but swapping the first two + * comparators instead. + */ + } + + // ------------------------------------------------------------------------ + // Methods from Collection + // ------------------------------------------------------------------------ + + @Override + public Iterator iterator() { + fLock.readLock().lock(); + try { + Iterable lastSnapshot = fLastSnapshot; + if (lastSnapshot == null) { + lastSnapshot = ImmutableList.copyOf(fStore); + fLastSnapshot = lastSnapshot; + } + return checkNotNull(lastSnapshot.iterator()); + } finally { + fLock.readLock().unlock(); + } + } + + @Override + public boolean add(@Nullable E val) { + if (val == null) { + throw new IllegalArgumentException("Cannot add null value"); //$NON-NLS-1$ + } + + fLock.writeLock().lock(); + try { + fStore.add(val); + // Go backwards to "sift up" like a priority queue + for (int i = size() - 1; i > 0 && COMPARATOR.compare(val, fStore.get(i - 1)) < 0; i--) { + Collections.swap(fStore, i, i - 1); + } + return true; + } finally { + fLock.writeLock().unlock(); + } + } + + @Override + public int size() { + return fStore.size(); + } + + @Override + public boolean isEmpty() { + return fStore.isEmpty(); + } + + @Override + public boolean contains(@Nullable Object o) { + fLock.readLock().lock(); + try { + return fStore.contains(o); + } finally { + fLock.readLock().unlock(); + } + } + + @Override + public boolean containsAll(@Nullable Collection c) { + fLock.readLock().lock(); + try { + return fStore.containsAll(c); + } finally { + fLock.readLock().unlock(); + } + } + + @Override + public Object[] toArray() { + fLock.readLock().lock(); + try { + return fStore.toArray(); + } finally { + fLock.readLock().unlock(); + } + } + + @Override + public T[] toArray(T[] a) { + fLock.readLock().lock(); + try { + return fStore.toArray(a); + } finally { + fLock.readLock().unlock(); + } + } + + @Override + public boolean remove(@Nullable Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(@Nullable Collection c) { + if (c == null) { + throw new IllegalArgumentException(); + } + + fLock.writeLock().lock(); + try { + boolean changed = false; + for (E elem : c) { + if (this.add(elem)) { + changed = true; + } + } + return changed; + } finally { + fLock.writeLock().unlock(); + } + } + + @Override + public boolean removeAll(@Nullable Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(@Nullable Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + fLock.writeLock().lock(); + try { + fStore.clear(); + } finally { + fLock.writeLock().unlock(); + } + } + + // ------------------------------------------------------------------------ + // Methods added by ISegmentStore + // ------------------------------------------------------------------------ + + @Override + public Iterable getIntersectingElements(long position) { + /* + * The intervals intersecting 't' are those whose 1) start time is + * *lower* than 't' AND 2) end time is *higher* than 't'. + */ + fLock.readLock().lock(); + try { + return fStore.stream().filter(element -> position >= element.getStart() && position <= element.getEnd()).collect(Collectors.toList()); + } finally { + fLock.readLock().unlock(); + } + } + + @Override + public Iterable getIntersectingElements(long start, long end) { + fLock.readLock().lock(); + try { + return fStore.stream().filter(element -> !(start > element.getEnd() || end < element.getStart())).collect(Collectors.toList()); + } finally { + fLock.readLock().unlock(); + } + } + + @Override + public void dispose() { + fLock.writeLock().lock(); + try { + fStore.clear(); + } finally { + fLock.writeLock().unlock(); + } + } +}