diff options
author | Geneviève Bastien | 2018-03-09 18:53:36 +0000 |
---|---|---|
committer | Genevieve Bastien | 2018-04-30 21:00:12 +0000 |
commit | fd68941c8c672f55f2a9b2ca09f059f7670560d8 (patch) | |
tree | aa8840ad1e11e9aff366c6d9bd7fdc4b34a2fb1c | |
parent | 4bd98ad1a400adac8c79c5e9e709a005aa28fae5 (diff) | |
download | org.eclipse.tracecompass.incubator-fd68941c8c672f55f2a9b2ca09f059f7670560d8.tar.gz org.eclipse.tracecompass.incubator-fd68941c8c672f55f2a9b2ca09f059f7670560d8.tar.xz org.eclipse.tracecompass.incubator-fd68941c8c672f55f2a9b2ca09f059f7670560d8.zip |
callstack: Add the flame chart data provider
And the unit tests.
Change-Id: I9d049f2e08d21b1c31064a987c67c9aed3b6e282
Signed-off-by: Geneviève Bastien <gbastien+lttng@versatic.net>
Reviewed-on: https://git.eclipse.org/r/119103
Tested-by: CI Bot
Reviewed-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Tested-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
19 files changed, 1758 insertions, 67 deletions
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/flamechart/CallStackTestBase.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/flamechart/CallStackTestBase.java index 13faa93e9..4420ac88c 100644 --- a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/flamechart/CallStackTestBase.java +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/flamechart/CallStackTestBase.java @@ -98,9 +98,9 @@ public class CallStackTestBase { * |-- tid3 3e2 --------------------------------20x * 5e3--6x 7e2--------13x * - * pid5 --- tid6 1e1 -----------------------------------20x - * | 2e3 ---------7x 12e4------------20x - * | 4e1--6x + * pid5 --- tid6 1e1 ----------------------------------------20x + * | 2e3 ---------7x 8e2---11x 12e4------------20x + * | 4e1--6x 9e3-10x * |-- tid7 1e5 -----------------------------------20x * 2e2 +++ 6x 9e2 ++++ 13x 15e2 ++ 19x * 10e3 + 11x diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/flamechart/FlameChartDataProviderTest.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/flamechart/FlameChartDataProviderTest.java new file mode 100644 index 000000000..b67b4c2e7 --- /dev/null +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/flamechart/FlameChartDataProviderTest.java @@ -0,0 +1,354 @@ +/******************************************************************************* + * Copyright (c) 2018 É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.incubator.callstack.core.tests.flamechart; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.incubator.callstack.core.tests.stubs.CallStackAnalysisStub; +import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartDataProvider; +import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartDataProviderFactory; +import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartEntryModel; +import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartEntryModel.EntryType; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.SelectionTimeQueryFilter; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.TimeQueryFilter; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.ITimeGraphRowModel; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.ITimeGraphState; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.TimeGraphState; +import org.eclipse.tracecompass.internal.provisional.tmf.core.response.ITmfResponse; +import org.eclipse.tracecompass.internal.provisional.tmf.core.response.TmfModelResponse; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +/** + * Test the {@link FlameChartDataProvider} class + * + * @author Geneviève Bastien + */ +@SuppressWarnings("restriction") +public class FlameChartDataProviderTest extends CallStackTestBase { + + private static final @Nullable IProgressMonitor MONITOR = new NullProgressMonitor(); + + private FlameChartDataProvider getDataProvider() { + CallStackAnalysisStub module = getModule(); + assertNotNull(module); + + FlameChartDataProviderFactory factory = new FlameChartDataProviderFactory(); + + FlameChartDataProvider dataProvider = (FlameChartDataProvider) factory.createProvider(getTrace(), module.getId()); + assertNotNull(dataProvider); + return dataProvider; + } + + /** + * Test getting the tree from the flame chart data provider + */ + @Test + public void testFetchTree() { + FlameChartDataProvider dataProvider = getDataProvider(); + + TmfModelResponse<List<FlameChartEntryModel>> responseTree = dataProvider.fetchTree(new TimeQueryFilter(0, Long.MAX_VALUE, 2), new NullProgressMonitor()); + assertTrue(responseTree.getStatus().equals(ITmfResponse.Status.COMPLETED)); + + // Test the size of the tree + List<FlameChartEntryModel> model = responseTree.getModel(); + assertNotNull(model); + assertEquals(18, model.size()); + + String traceName = getTrace().getName(); + + // Test the hierarchy of the tree + for (FlameChartEntryModel entry : model) { + FlameChartEntryModel parent = findEntryById(model, entry.getParentId()); + switch (entry.getEntryType()) { + case FUNCTION: + assertNotNull(parent); + assertEquals(EntryType.LEVEL, parent.getEntryType()); + break; + case LEVEL: { + assertNotNull(parent); + // Verify the hierarchy of the elements + switch (entry.getName()) { + case "1": + assertEquals(traceName, parent.getName()); + break; + case "2": + assertEquals("1", parent.getName()); + break; + case "3": + assertEquals("1", parent.getName()); + break; + case "5": + assertEquals(traceName, parent.getName()); + break; + case "6": + assertEquals("5", parent.getName()); + break; + case "7": + assertEquals("5", parent.getName()); + break; + default: + fail("Unknown entry " + entry.getName()); + break; + } + } + break; + case KERNEL: + fail("There should be no kernel entry in this callstack"); + break; + case TRACE: + assertEquals(-1, entry.getParentId()); + break; + default: + fail("Unknown entry " + entry); + break; + } + } + } + + private static @Nullable FlameChartEntryModel findEntryById(Collection<FlameChartEntryModel> list, long id) { + return list.stream() + .filter(entry -> entry.getId() == id) + .findFirst().orElse(null); + } + + private static @Nullable FlameChartEntryModel findEntryByNameAndType(Collection<FlameChartEntryModel> list, String name, EntryType type) { + return list.stream() + .filter(entry -> entry.getEntryType().equals(type) && entry.getName().equals(name)) + .findFirst().orElse(null); + } + + private static @Nullable FlameChartEntryModel findEntryByDepthAndType(Collection<FlameChartEntryModel> list, int depth, EntryType type) { + return list.stream() + .filter(entry -> entry.getEntryType().equals(type) && entry.getDepth() == depth) + .findFirst().orElse(null); + } + + private static List<FlameChartEntryModel> findEntriesByParent(Collection<FlameChartEntryModel> list, long parentId) { + return list.stream() + .filter(entry -> entry.getParentId() == parentId) + .collect(Collectors.toList()); + } + + /** + * Test getting the model from the flame chart data provider + */ + @Test + public void testFetchModel() { + FlameChartDataProvider dataProvider = getDataProvider(); + + TmfModelResponse<List<FlameChartEntryModel>> responseTree = dataProvider.fetchTree(new TimeQueryFilter(0, Long.MAX_VALUE, 2), new NullProgressMonitor()); + assertTrue(responseTree.getStatus().equals(ITmfResponse.Status.COMPLETED)); + List<FlameChartEntryModel> model = responseTree.getModel(); + + // Find the entries corresponding to threads 3 and 6 (along with pid 5) + Set<@NonNull Long> selectedIds = new HashSet<>(); + // Thread 3 + FlameChartEntryModel tid3 = findEntryByNameAndType(model, "3", EntryType.LEVEL); + assertNotNull(tid3); + selectedIds.add(tid3.getId()); + List<FlameChartEntryModel> tid3Children = findEntriesByParent(model, tid3.getId()); + assertEquals(2, tid3Children.size()); + tid3Children.forEach(child -> selectedIds.add(child.getId())); + // Pid 5 + FlameChartEntryModel pid5 = findEntryByNameAndType(model, "5", EntryType.LEVEL); + assertNotNull(pid5); + selectedIds.add(pid5.getId()); + // Thread 6 + FlameChartEntryModel tid6 = findEntryByNameAndType(model, "6", EntryType.LEVEL); + assertNotNull(tid6); + selectedIds.add(tid6.getId()); + List<FlameChartEntryModel> tid6Children = findEntriesByParent(model, tid6.getId()); + assertEquals(3, tid6Children.size()); + tid6Children.forEach(child -> selectedIds.add(child.getId())); + + // Get the row model for those entries with high resolution + TmfModelResponse<List<ITimeGraphRowModel>> rowModel = dataProvider.fetchRowModel(new SelectionTimeQueryFilter(3, 15, 50, selectedIds), new NullProgressMonitor()); + assertEquals(ITmfResponse.Status.COMPLETED, rowModel.getStatus()); + + List<ITimeGraphRowModel> rowModels = rowModel.getModel(); + assertNotNull(rowModels); + assertEquals(8, rowModels.size()); + + // Verify the level entries + verifyStates(rowModels, tid3, Collections.emptyList()); + verifyStates(rowModels, pid5, Collections.emptyList()); + verifyStates(rowModels, tid6, Collections.emptyList()); + // Verify function level 1 of tid 3 + verifyStates(rowModels, findEntryByDepthAndType(tid3Children, 1, EntryType.FUNCTION), ImmutableList.of(new TimeGraphState(3, 17, Long.MIN_VALUE, "op2"))); + // Verify function level 2 of tid 3 + verifyStates(rowModels, findEntryByDepthAndType(tid3Children, 2, EntryType.FUNCTION), ImmutableList.of( + new TimeGraphState(1, 4, Long.MIN_VALUE), + new TimeGraphState(5, 1, Long.MIN_VALUE, "op3"), + new TimeGraphState(6, 1, Long.MIN_VALUE), + new TimeGraphState(7, 6, Long.MIN_VALUE, "op2"), + new TimeGraphState(13, 8, Long.MIN_VALUE))); + // Verify function level 1 of tid 6 + verifyStates(rowModels, findEntryByDepthAndType(tid6Children, 1, EntryType.FUNCTION), ImmutableList.of(new TimeGraphState(1, 19, Long.MIN_VALUE, "op1"))); + // Verify function level 2 of tid 6 + verifyStates(rowModels, findEntryByDepthAndType(tid6Children, 2, EntryType.FUNCTION), ImmutableList.of( + new TimeGraphState(2, 5, Long.MIN_VALUE, "op3"), + new TimeGraphState(7, 1, Long.MIN_VALUE), + new TimeGraphState(8, 3, Long.MIN_VALUE, "op2"), + new TimeGraphState(11, 1, Long.MIN_VALUE), + new TimeGraphState(12, 8, Long.MIN_VALUE, "op4"))); + // Verify function level 3 of tid 6 + verifyStates(rowModels, findEntryByDepthAndType(tid6Children, 3, EntryType.FUNCTION), ImmutableList.of( + new TimeGraphState(1, 3, Long.MIN_VALUE), + new TimeGraphState(4, 2, Long.MIN_VALUE, "op1"), + new TimeGraphState(6, 3, Long.MIN_VALUE), + new TimeGraphState(9, 1, Long.MIN_VALUE, "op3"), + new TimeGraphState(10, 11, Long.MIN_VALUE))); + + // Get the row model for those entries with low resolution + rowModel = dataProvider.fetchRowModel(new SelectionTimeQueryFilter(3, 15, 2, selectedIds), new NullProgressMonitor()); + assertEquals(ITmfResponse.Status.COMPLETED, rowModel.getStatus()); + + rowModels = rowModel.getModel(); + assertNotNull(rowModels); + assertEquals(8, rowModels.size()); + + // Verify the level entries + verifyStates(rowModels, tid3, Collections.emptyList()); + verifyStates(rowModels, pid5, Collections.emptyList()); + verifyStates(rowModels, tid6, Collections.emptyList()); + // Verify function level 1 of tid 3 + verifyStates(rowModels, findEntryByDepthAndType(tid3Children, 1, EntryType.FUNCTION), ImmutableList.of(new TimeGraphState(3, 17, Long.MIN_VALUE, "op2"))); + // Verify function level 2 of tid 3 + verifyStates(rowModels, findEntryByDepthAndType(tid3Children, 2, EntryType.FUNCTION), ImmutableList.of( + new TimeGraphState(1, 4, Long.MIN_VALUE), + new TimeGraphState(13, 8, Long.MIN_VALUE))); + // Verify function level 1 of tid 6 + verifyStates(rowModels, findEntryByDepthAndType(tid6Children, 1, EntryType.FUNCTION), ImmutableList.of(new TimeGraphState(1, 19, Long.MIN_VALUE, "op1"))); + // Verify function level 2 of tid 6 + verifyStates(rowModels, findEntryByDepthAndType(tid6Children, 2, EntryType.FUNCTION), ImmutableList.of( + new TimeGraphState(2, 5, Long.MIN_VALUE, "op3"), + new TimeGraphState(12, 8, Long.MIN_VALUE, "op4"))); + // Verify function level 3 of tid 6 + verifyStates(rowModels, findEntryByDepthAndType(tid6Children, 3, EntryType.FUNCTION), ImmutableList.of( + new TimeGraphState(1, 3, Long.MIN_VALUE), + new TimeGraphState(10, 11, Long.MIN_VALUE))); + } + + /** + * Test following a callstack backward and forward + */ + @Test + public void testFollowEvents() { + FlameChartDataProvider dataProvider = getDataProvider(); + + TmfModelResponse<List<FlameChartEntryModel>> responseTree = dataProvider.fetchTree(new TimeQueryFilter(0, Long.MAX_VALUE, 2), new NullProgressMonitor()); + assertTrue(responseTree.getStatus().equals(ITmfResponse.Status.COMPLETED)); + List<FlameChartEntryModel> model = responseTree.getModel(); + + // Thread 2 + FlameChartEntryModel tid2 = findEntryByNameAndType(model, "2", EntryType.LEVEL); + assertNotNull(tid2); + List<FlameChartEntryModel> tid2Children = findEntriesByParent(model, tid2.getId()); + assertEquals(3, tid2Children.size()); + + // For each child, make sure the response is always the same + for (FlameChartEntryModel tid2Child : tid2Children) { + TmfModelResponse<List<ITimeGraphRowModel>> rowModel = dataProvider.fetchRowModel(new SelectionTimeQueryFilter(6, Long.MAX_VALUE, 2, Collections.singleton(tid2Child.getId())), MONITOR); + verifyFollowResponse(rowModel, 1, 7); + } + + // Go forward from time 7 till the end for one of the child element + Set<@NonNull Long> selectedEntry = Objects.requireNonNull(Collections.singleton(tid2Children.get(1).getId())); + TmfModelResponse<List<ITimeGraphRowModel>> rowModel = dataProvider.fetchRowModel(new SelectionTimeQueryFilter(7, Long.MAX_VALUE, 2, selectedEntry), MONITOR); + verifyFollowResponse(rowModel, 0, 10); + + rowModel = dataProvider.fetchRowModel(new SelectionTimeQueryFilter(10, Long.MAX_VALUE, 2, selectedEntry), new NullProgressMonitor()); + verifyFollowResponse(rowModel, 1, 12); + + rowModel = dataProvider.fetchRowModel(new SelectionTimeQueryFilter(12, Long.MAX_VALUE, 2, selectedEntry), new NullProgressMonitor()); + verifyFollowResponse(rowModel, 0, 20); + + rowModel = dataProvider.fetchRowModel(new SelectionTimeQueryFilter(20, Long.MAX_VALUE, 2, selectedEntry), new NullProgressMonitor()); + verifyFollowResponse(rowModel, -1, -1); + + // Go backward from the back + rowModel = dataProvider.fetchRowModel(new SelectionTimeQueryFilter(Lists.newArrayList(Long.MIN_VALUE, 20L), selectedEntry), new NullProgressMonitor()); + verifyFollowResponse(rowModel, 1, 12); + + // Go backward from time 7 till the beginning + rowModel = dataProvider.fetchRowModel(new SelectionTimeQueryFilter(Lists.newArrayList(Long.MIN_VALUE, 7L), selectedEntry), new NullProgressMonitor()); + verifyFollowResponse(rowModel, 2, 5); + + rowModel = dataProvider.fetchRowModel(new SelectionTimeQueryFilter(Lists.newArrayList(Long.MIN_VALUE, 5L), selectedEntry), new NullProgressMonitor()); + verifyFollowResponse(rowModel, 3, 4); + + rowModel = dataProvider.fetchRowModel(new SelectionTimeQueryFilter(Lists.newArrayList(Long.MIN_VALUE, 4L), selectedEntry), new NullProgressMonitor()); + verifyFollowResponse(rowModel, 2, 3); + + rowModel = dataProvider.fetchRowModel(new SelectionTimeQueryFilter(Lists.newArrayList(Long.MIN_VALUE, 3L), selectedEntry), new NullProgressMonitor()); + verifyFollowResponse(rowModel, 1, 1); + + rowModel = dataProvider.fetchRowModel(new SelectionTimeQueryFilter(Lists.newArrayList(Long.MIN_VALUE, 1L), selectedEntry), new NullProgressMonitor()); + verifyFollowResponse(rowModel, -1, -1); + } + + private static void verifyFollowResponse(TmfModelResponse<List<ITimeGraphRowModel>> rowModel, int expectedDepth, int expectedTime) { + assertEquals(ITmfResponse.Status.COMPLETED, rowModel.getStatus()); + + List<ITimeGraphRowModel> rowModels = rowModel.getModel(); + if (expectedDepth < 0) { + assertNull(rowModels); + return; + } + assertNotNull(rowModels); + assertEquals(1, rowModels.size()); + List<ITimeGraphState> row = rowModels.get(0).getStates(); + assertEquals(1, row.size()); + ITimeGraphState stackInterval = row.get(0); + long depth = stackInterval.getValue(); + assertEquals(expectedDepth, depth); + assertEquals(expectedTime, stackInterval.getStartTime()); + } + + private static void verifyStates(List<ITimeGraphRowModel> rowModels, FlameChartEntryModel entry, List<TimeGraphState> expectedStates) { + assertNotNull(entry); + ITimeGraphRowModel rowModel = rowModels.stream() + .filter(model -> model.getEntryID() == entry.getId()) + .findFirst().orElse(null); + assertNotNull(rowModel); + List<ITimeGraphState> states = rowModel.getStates(); + for (int i = 0; i < states.size(); i++) { + if (i > expectedStates.size() - 1) { + fail("Unexpected state at position " + i + " for entry " + entry.getName() + ": " + states.get(i)); + } + ITimeGraphState actual = states.get(i); + ITimeGraphState expected = expectedStates.get(i); + assertEquals("State start time at " + i + " for entry " + entry.getName(), expected.getStartTime(), actual.getStartTime()); + assertEquals("Duration at " + i + " for entry " + entry.getName(), expected.getDuration(), actual.getDuration()); + assertEquals("Label at " + i + " for entry " + entry.getName(), expected.getLabel(), actual.getLabel()); + + } + } + +} diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/META-INF/MANIFEST.MF b/callstack/org.eclipse.tracecompass.incubator.callstack.core/META-INF/MANIFEST.MF index 19d018401..24f1c8ada 100644 --- a/callstack/org.eclipse.tracecompass.incubator.callstack.core/META-INF/MANIFEST.MF +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/META-INF/MANIFEST.MF @@ -34,9 +34,11 @@ Export-Package: org.eclipse.tracecompass.incubator.callstack.core.base, org.eclipse.tracecompass.incubator.internal.callstack.core.base;x-friends:="org.eclipse.tracecompass.incubator.callstack.core.tests,org.eclipse.tracecompass.incubator.callstack.ui", org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented;x-friends:="org.eclipse.tracecompass.incubator.callstack.core.tests,org.eclipse.tracecompass.incubator.callstack.ui", org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.callgraph;x-friends:="org.eclipse.tracecompass.incubator.callstack.core.tests,org.eclipse.tracecompass.incubator.callstack.ui", + org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider, org.eclipse.tracecompass.incubator.internal.callstack.core.symbol;x-internal:=true, org.eclipse.tracecompass.incubator.internal.callstack.core.xml.callstack;x-friends:="org.eclipse.tracecompass.incubator.callstack.core.tests,org.eclipse.tracecompass.incubator.callstack.ui" Import-Package: com.google.common.annotations, - com.google.common.collect, com.google.common.base, + com.google.common.cache, + com.google.common.collect, org.apache.commons.lang3 diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/plugin.xml b/callstack/org.eclipse.tracecompass.incubator.callstack.core/plugin.xml index 1cd694bc3..253fe1192 100644 --- a/callstack/org.eclipse.tracecompass.incubator.callstack.core/plugin.xml +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/plugin.xml @@ -21,4 +21,11 @@ </tracetype> </module> </extension> + <extension + point="org.eclipse.tracecompass.tmf.core.dataprovider"> + <dataProviderFactory + class="org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartDataProviderFactory" + id="org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.flamechart"> + </dataProviderFactory> + </extension> </plugin> diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/flamechart/CallStack.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/flamechart/CallStack.java index 733e4cae6..4a171c832 100644 --- a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/flamechart/CallStack.java +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/flamechart/CallStack.java @@ -10,8 +10,10 @@ package org.eclipse.tracecompass.incubator.callstack.core.flamechart; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import org.eclipse.core.runtime.IProgressMonitor; @@ -129,43 +131,24 @@ public class CallStack { } } - private String getHostId(long time) { - return fHostProvider.apply(time); - } - /** - * Get the function call with closest beginning or end from time, either - * forward or backward. + * Get the quark for a given depth * - * @param time - * The time of query - * @param forward - * Set to <code>true</code> if the beginning or end is forward in - * time, <code>false</code> to go backwards - * @return The next function + * @param depth + * The requested depth + * @return Get the quark for the function at a given depth */ - public @Nullable ICalledFunction getNextFunction(long time, boolean forward) { - // From the bottom of the stack, query at time t to find the last level - // with an active call - try { - for (int i = fQuarks.size() - 1; i >= 0; i--) { - ITmfStateInterval interval; - - interval = fStateSystem.querySingleState(time, fQuarks.get(i)); - if (!interval.getStateValue().isNull()) { - - } - } - } catch (StateSystemDisposedException e) { - - } - return null; + public Integer getQuarkAtDepth(int depth) { + return fQuarks.get(depth - 1); + } + private String getHostId(long time) { + return fHostProvider.apply(time); } /** - * Get the function call at a given depth that either begins or ends after - * the requested time. + * Get the function call at a given depth that either begins or ends after the + * requested time. * * @param time * The time to query @@ -232,7 +215,43 @@ public class CallStack { interval = fStateSystem.querySingleState(interval.getEndTime() + 1, fQuarks.get(depth - 1)); } if (!interval.getStateValue().isNull() && interval.getStartTime() < end) { - return CalledFunctionFactory.create(Math.max(start, interval.getStartTime()), Math.min(end, interval.getEndTime() + 1), interval.getValue(), getSymbolKeyAt(interval.getStartTime()), getThreadId(interval.getStartTime()), parent, model); + return CalledFunctionFactory.create(Math.max(start, interval.getStartTime()), Math.min(end, interval.getEndTime() + 1), interval.getValue(), getSymbolKeyAt(interval.getStartTime()), getThreadId(interval.getStartTime()), parent, + model); + } + } catch (StateSystemDisposedException e) { + + } + return null; + } + + /** + * Get the next depth of this callstack, from the selected time. This function + * is used to navigate the callstack forward or backward + * + * @param time + * The reference time from which to calculate the next depth + * @param forward + * If <code>true</code>, the returned depth is the next depth after + * the requested time, otherwise, it will return the next depth + * before. + * @return The interval whose value is a number referring to the next depth (or + * null if the stack is empty at this time), or <code>null</code> if + * there is no next depth to this callstack + */ + public @Nullable ITmfStateInterval getNextDepth(long time, boolean forward) { + Integer oneQuark = fQuarks.get(0); + int parent = fStateSystem.getParentAttributeQuark(oneQuark); + long queryTime = Long.max(fStateSystem.getStartTime(), Long.min(time, fStateSystem.getCurrentEndTime())); + try { + ITmfStateInterval currentDepth = fStateSystem.querySingleState(queryTime, parent); + ITmfStateInterval interval = null; + if (forward && currentDepth.getEndTime() + 1 <= fStateSystem.getCurrentEndTime()) { + interval = fStateSystem.querySingleState(currentDepth.getEndTime() + 1, parent); + } else if (!forward && currentDepth.getStartTime() - 1 >= fStateSystem.getStartTime()) { + interval = fStateSystem.querySingleState(currentDepth.getStartTime() - 1, parent); + } + if (interval != null) { + return interval; } } catch (StateSystemDisposedException e) { @@ -270,6 +289,18 @@ public class CallStack { } /** + * Update the quarks list. Only the quarks at positions higher than the size of + * the quarks will be copied in the list. The ones currently present should not + * change. + * + * @param subAttributes + * The new complete list of attributes + */ + public void updateAttributes(List<Integer> subAttributes) { + fQuarks.addAll(fQuarks.size(), subAttributes.subList(fQuarks.size(), subAttributes.size())); + } + + /** * Get the ID of the thread running this callstack at time t. This method is * used in conjunction with other trace data to get the time spent on the * CPU for this call. @@ -303,6 +334,18 @@ public class CallStack { } /** + * Get the unique {@link HostThread} for this callstack. This returns a value only if the TID is not variable in time _and_ it is defined + * + * @return The {@link HostThread} that spans this callstack or <code>null</code> if TID is variable or it is not defined. + */ + public @Nullable HostThread getHostThread() { + if (!isTidVariable()) { + return getHostThread(fStateSystem.getStartTime()); + } + return null; + } + + /** * Return whether the TID is variable through time for this callstack or if it * fixed * @@ -407,16 +450,76 @@ public class CallStack { * * @param function * The function for which to get the kernel statuses - * @param resolution - * The resolution, ie the number of nanoseconds between kernel status - * queries. A value lower or equal to 1 will return all intervals. + * @param times + * The times at which to query kernel statuses. An empty collection + * will return all intervals. * @return An iterator over the kernel status. The iterator can be empty is * statuses are not available or if the function is outside the range of * the available data. */ - public Iterable<ProcessStatusInterval> getKernelStatuses(ICalledFunction function, long resolution) { + public Iterable<ProcessStatusInterval> getKernelStatuses(ICalledFunction function, Collection<Long> times) { IHostModel model = ModelManager.getModelFor(getHostId(function.getStart())); + int resolution = 1; + // Filter the times + if (!times.isEmpty()) { + // Filter the times overlapping this function and calculate a resolution + List<Long> filtered = new ArrayList<>(); + for (Long time : times) { + if (function.intersects(time)) { + filtered.add(time); + } + } + Collections.sort(filtered); + resolution = !filtered.isEmpty() ? (int) (filtered.get(filtered.size() - 1) - filtered.get(0) / filtered.size()) : resolution; + return model.getThreadStatusIntervals(function.getThreadId(), function.getStart(), function.getEnd(), resolution); + } return model.getThreadStatusIntervals(function.getThreadId(), function.getStart(), function.getEnd(), resolution); } + /** + * Transforms a state interval from the state system into a + * {@link ICalledFunction}. The function allows to retrieve data from this + * function, for instance, the thread ID, the symbol provider, etc. + * + * Note: It is the responsibility of the caller to make sure that the interval + * does not have a null-value, otherwise, a NullPointerException will be thrown. + * + * @param callInterval + * The state interval + * @return An {@link ICalledFunction} object, with its fields resolved + */ + public ICalledFunction getFunctionFromInterval(ITmfStateInterval callInterval) { + int threadId = getThreadId(callInterval.getStartTime()); + return CalledFunctionFactory.create(callInterval.getStartTime(), + callInterval.getEndTime() + 1, + callInterval.getValue(), + getSymbolKeyAt(callInterval.getStartTime()), + threadId, + null, + ModelManager.getModelFor(getHostId(callInterval.getStartTime()))); + } + + @Override + public int hashCode() { + return Objects.hash(fSymbolKeyElement, fThreadIdProvider, fStateSystem, fQuarks, fHostProvider); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof CallStack)) { + return false; + } + CallStack other = (CallStack) obj; + return (Objects.equals(fSymbolKeyElement, other.fSymbolKeyElement) && + Objects.equals(fThreadIdProvider, other.fThreadIdProvider) && + Objects.equals(fStateSystem, other.fStateSystem) && + Objects.equals(fQuarks, other.fQuarks) && + Objects.equals(fHostProvider, other.fHostProvider)); + } + + @Override + public String toString() { + return "Callstack for quarks " + fQuarks; //$NON-NLS-1$ + } + } diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/CallStackDepth.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/CallStackDepth.java new file mode 100644 index 000000000..fbcf8da16 --- /dev/null +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/CallStackDepth.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2018 É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.incubator.callstack.core.instrumented; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.incubator.callstack.core.flamechart.CallStack; + +import com.google.common.base.Objects; + +/** + * A class that associates a callstack with a depth, to abstract the state + * system accesses. + * + * @author Geneviève Bastien + */ +public class CallStackDepth { + + private final CallStack fCallstack; + private final int fDepth; + + /** + * Constructor. The caller must make sure that the callstack has the requested + * depth. + * + * @param callstack + * The callstack + * @param depth + * The depth of the callstack + */ + public CallStackDepth(CallStack callstack, int depth) { + fCallstack = callstack; + fDepth = depth; + } + + /** + * Get the quark corresponding to this callstack depth + * + * @return The quark at this depth + */ + public int getQuark() { + return fCallstack.getQuarkAtDepth(fDepth); + } + + /** + * Get the callstack corresponding to this callstack depth + * + * @return The callstack + */ + public CallStack getCallStack() { + return fCallstack; + } + + @Override + public int hashCode() { + return Objects.hashCode(fCallstack, fDepth); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof CallStackDepth)) { + return false; + } + CallStackDepth csd = (CallStackDepth) obj; + return Objects.equal(fCallstack, csd.fCallstack) && (fDepth == csd.fDepth); + } + + @Override + public String toString() { + return fCallstack + " at depth " + fDepth; //$NON-NLS-1$ + } + +} diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/IFlameChartProvider.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/IFlameChartProvider.java index 9dbf48565..179bca38f 100644 --- a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/IFlameChartProvider.java +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/IFlameChartProvider.java @@ -9,12 +9,19 @@ package org.eclipse.tracecompass.incubator.callstack.core.instrumented; +import java.util.Collection; +import java.util.Objects; + import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tracecompass.analysis.timing.core.segmentstore.ISegmentStoreProvider; import org.eclipse.tracecompass.incubator.callstack.core.instrumented.statesystem.CallStackHostUtils; import org.eclipse.tracecompass.incubator.callstack.core.instrumented.statesystem.CallStackSeries; +import org.eclipse.tracecompass.segmentstore.core.ISegment; import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; + /** * Interface that can be implemented by components who provide call stacks as * part of their data. @@ -38,4 +45,33 @@ public interface IFlameChartProvider extends IAnalysisModule, ISegmentStoreProvi * @return The ID of the host */ String getHostId(); + + /** + * Return whether this analysis is complete + * + * @return <code>true</code> if the analysis is completed, whether failed or + * not, <code>false</code> if it is currently running + */ + boolean isComplete(); + + /** + * Query the requested callstacks and return the segments for the sampled times. + * The returned segments will be simply {@link ISegment} when there is no + * function at a given depth, or {@link ICalledFunction} when there is an actual + * function. + * + * @param collection + * The callstack entries to query + * @param times + * The complete list of times to query, they may not all be within + * this series's range + * @return A map of callstack depths to a list of segments. + */ + default Multimap<CallStackDepth, ISegment> queryCallStacks(Collection<CallStackDepth> collection, Collection<Long> times) { + CallStackSeries callStackSeries = getCallStackSeries(); + if (callStackSeries == null) { + return Objects.requireNonNull(ArrayListMultimap.create()); + } + return callStackSeries.queryCallStacks(collection, times); + } } diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/statesystem/CallStackSeries.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/statesystem/CallStackSeries.java index 1196cdcd9..5d669c434 100644 --- a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/statesystem/CallStackSeries.java +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/statesystem/CallStackSeries.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -27,6 +28,7 @@ import org.eclipse.tracecompass.incubator.analysis.core.model.ModelManager; import org.eclipse.tracecompass.incubator.callstack.core.base.ICallStackElement; import org.eclipse.tracecompass.incubator.callstack.core.base.ICallStackGroupDescriptor; import org.eclipse.tracecompass.incubator.callstack.core.flamechart.CallStack; +import org.eclipse.tracecompass.incubator.callstack.core.instrumented.CallStackDepth; import org.eclipse.tracecompass.incubator.callstack.core.instrumented.ICalledFunction; import org.eclipse.tracecompass.incubator.callstack.core.instrumented.statesystem.CallStackHostUtils.IHostIdProvider; import org.eclipse.tracecompass.incubator.callstack.core.instrumented.statesystem.CallStackHostUtils.IHostIdResolver; @@ -34,15 +36,20 @@ import org.eclipse.tracecompass.incubator.internal.callstack.core.Activator; import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.InstrumentedCallStackElement; import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.InstrumentedGroupDescriptor; import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.callgraph.CalledFunctionFactory; +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.statesystem.core.ITmfStateSystem; import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException; +import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; import com.google.common.base.Function; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; /** * A callstack series contain the information necessary to build all the @@ -141,8 +148,10 @@ public class CallStackSeries implements ISegmentStore<ISegment> { public int getThreadId(long time) { ITmfStateInterval interval = fInterval; int tid = fLastThreadId; - if (interval != null && interval.intersects(time)) { - return fLastThreadId; + // If interval is not null and either the tid does not vary in time or the + // interval intersects the requested time + if (interval != null && (!fVariesInTime || interval.intersects(time))) { + return tid; } try { interval = fSs.querySingleState(time, fQuark); @@ -402,6 +411,8 @@ public class CallStackSeries implements ISegmentStore<ISegment> { private final String fName; private final @Nullable IThreadIdResolver fResolver; private final IHostIdResolver fHostResolver; + private final ITmfStateSystem fStateSystem; + private final Map<Integer, ICallStackElement> fRootElements = new HashMap<>(); /** * Constructor @@ -434,6 +445,7 @@ public class CallStackSeries implements ISegmentStore<ISegment> { InstrumentedGroupDescriptor level = new InstrumentedGroupDescriptor(ss, patternPaths.get(i), prevLevel, symbolKeyLevelIndex == i ? true : false); prevLevel = level; } + fStateSystem = ss; fRootGroup = prevLevel; fName = name; fResolver = threadResolver; @@ -446,7 +458,7 @@ public class CallStackSeries implements ISegmentStore<ISegment> { * @return The root elements of the callstack series */ public Collection<ICallStackElement> getRootElements() { - return InstrumentedCallStackElement.getRootElements(fRootGroup, fHostResolver, fResolver); + return InstrumentedCallStackElement.getRootElements(fRootGroup, fHostResolver, fResolver, fRootElements); } /** @@ -467,6 +479,53 @@ public class CallStackSeries implements ISegmentStore<ISegment> { return fName; } + /** + * Query the requested callstacks and return the segments for the sampled times. + * The returned segments will be simply {@link ISegment} when there is no + * function at a given depth, or {@link ICalledFunction} when there is an actual + * function. + * + * @param callstacks + * The callstack entries to query + * @param times + * The complete list of times to query, they may not all be within + * this series's range + * @return A map of callstack depths to a list of segments. + */ + public Multimap<CallStackDepth, ISegment> queryCallStacks(Collection<CallStackDepth> callstacks, Collection<Long> times) { + Map<Integer, CallStackDepth> quarks = Maps.uniqueIndex(callstacks, cs -> cs.getQuark()); + Multimap<CallStackDepth, ISegment> map = Objects.requireNonNull(ArrayListMultimap.create()); + Collection<Long> queryTimes = getTimes(fStateSystem, times); + try { + Iterable<ITmfStateInterval> query2d = fStateSystem.query2D(quarks.keySet(), queryTimes); + for (ITmfStateInterval callInterval : query2d) { + CallStackDepth callStackDepth = Objects.requireNonNull(quarks.get(callInterval.getAttribute())); + if (callInterval.getValue() != null) { + map.put(callStackDepth, callStackDepth.getCallStack().getFunctionFromInterval(callInterval)); + } else { + map.put(callStackDepth, new BasicSegment(callInterval.getStartTime(), callInterval.getEndTime() + 1)); + } + } + } catch (IndexOutOfBoundsException | TimeRangeException | StateSystemDisposedException e) { + e.printStackTrace(); + } + return map; + } + + private static Collection<Long> getTimes(ITmfStateSystem ss, Collection<Long> times) { + // Filter and deduplicate the time stamps for the statesystem + long start = ss.getStartTime(); + long end = ss.getCurrentEndTime(); + // use a HashSet to deduplicate time stamps + Collection<Long> queryTimes = new HashSet<>(); + for (long t : times) { + if (t >= start && t <= end) { + queryTimes.add(t); + } + } + return queryTimes; + } + // --------------------------------------------------- // Segment store methods // --------------------------------------------------- diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/statesystem/InstrumentedCallStackAnalysis.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/statesystem/InstrumentedCallStackAnalysis.java index 0bf82880c..3613ea3d4 100644 --- a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/statesystem/InstrumentedCallStackAnalysis.java +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/callstack/core/instrumented/statesystem/InstrumentedCallStackAnalysis.java @@ -324,4 +324,17 @@ public abstract class InstrumentedCallStackAnalysis extends TmfStateSystemAnalys return Collections.emptyList(); } + @Override + public boolean isComplete() { + // Initialization error, but the analysis is completed + if (!waitForInitialization()) { + return true; + } + ITmfStateSystem stateSystem = getStateSystem(); + if (stateSystem == null) { + throw new IllegalStateException("The initialiation is complete, so the state system must not be null"); //$NON-NLS-1$ + } + return stateSystem.waitUntilBuilt(0); + } + }
\ No newline at end of file diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/InstrumentedCallStackElement.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/InstrumentedCallStackElement.java index 07cdf5766..85d9ee65f 100644 --- a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/InstrumentedCallStackElement.java +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/InstrumentedCallStackElement.java @@ -12,8 +12,11 @@ package org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; @@ -46,8 +49,8 @@ public class InstrumentedCallStackElement extends CallStackElement { private final int fQuark; private final IHostIdResolver fHostResolver; private final @Nullable IThreadIdResolver fThreadIdResolver; + private final Map<Integer, ICallStackElement> fNextElements = new HashMap<>(); - private @Nullable Collection<ICallStackElement> fChildren; private @Nullable CallStack fCallstack = null; /** @@ -83,19 +86,12 @@ public class InstrumentedCallStackElement extends CallStackElement { @Override public Collection<ICallStackElement> getChildren() { - Collection<ICallStackElement> children = fChildren; - if (children == null) { - // Get the elements from the next group in the hierarchy - @Nullable - ICallStackGroupDescriptor nextGroup = getNextGroup(); - if (!(nextGroup instanceof InstrumentedGroupDescriptor)) { - children = Collections.EMPTY_LIST; - } else { - children = getNextGroupElements((InstrumentedGroupDescriptor) nextGroup); - } - fChildren = children; + // Get the elements from the next group in the hierarchy + @Nullable ICallStackGroupDescriptor nextGroup = getNextGroup(); + if (!(nextGroup instanceof InstrumentedGroupDescriptor)) { + return Collections.emptyList(); } - return children; + return getNextGroupElements((InstrumentedGroupDescriptor) nextGroup); } @Override @@ -112,18 +108,21 @@ public class InstrumentedCallStackElement extends CallStackElement { * The host ID resolver * @param resolver * the thread ID resolver + * @param cache + * A cache of elements already built. It maps a quark to an element + * and the element will be returned if it has already been computed * @return A collection of elements that are roots of the given callstack * grouping */ - public static Collection<ICallStackElement> getRootElements(InstrumentedGroupDescriptor rootGroup, IHostIdResolver hostResolver, @Nullable IThreadIdResolver resolver) { - return getNextElements(rootGroup, rootGroup.getStateSystem(), ITmfStateSystem.ROOT_ATTRIBUTE, hostResolver, resolver, null); + public static Collection<ICallStackElement> getRootElements(InstrumentedGroupDescriptor rootGroup, IHostIdResolver hostResolver, @Nullable IThreadIdResolver resolver, Map<Integer, ICallStackElement> cache) { + return getNextElements(rootGroup, rootGroup.getStateSystem(), ITmfStateSystem.ROOT_ATTRIBUTE, hostResolver, resolver, null, cache); } private Collection<ICallStackElement> getNextGroupElements(InstrumentedGroupDescriptor nextGroup) { - return getNextElements(nextGroup, fStateSystem, fQuark, fHostResolver, fThreadIdResolver, this); + return getNextElements(nextGroup, fStateSystem, fQuark, fHostResolver, fThreadIdResolver, this, fNextElements); } - private static Collection<ICallStackElement> getNextElements(InstrumentedGroupDescriptor nextGroup, ITmfStateSystem stateSystem, int baseQuark, IHostIdResolver hostResolver, @Nullable IThreadIdResolver threadIdProvider, @Nullable InstrumentedCallStackElement parent) { + private static Collection<ICallStackElement> getNextElements(InstrumentedGroupDescriptor nextGroup, ITmfStateSystem stateSystem, int baseQuark, IHostIdResolver hostResolver, @Nullable IThreadIdResolver threadIdProvider, @Nullable InstrumentedCallStackElement parent, Map<Integer, ICallStackElement> cache) { // Get the elements from the base quark at the given pattern List<Integer> quarks = stateSystem.getQuarks(baseQuark, nextGroup.getSubPattern()); if (quarks.isEmpty()) { @@ -134,10 +133,14 @@ public class InstrumentedCallStackElement extends CallStackElement { // If the next level is null, then this is a callstack final element List<ICallStackElement> elements = new ArrayList<>(quarks.size()); for (Integer quark : quarks) { - InstrumentedCallStackElement element = new InstrumentedCallStackElement(hostResolver, stateSystem, quark, - nextGroup, nextLevel, threadIdProvider, parent); - if (nextGroup.isSymbolKeyGroup()) { - element.setSymbolKeyElement(element); + ICallStackElement element = cache.get(quark); + if (element == null) { + element = new InstrumentedCallStackElement(hostResolver, stateSystem, quark, + nextGroup, nextLevel, threadIdProvider, parent); + if (nextGroup.isSymbolKeyGroup()) { + element.setSymbolKeyElement(element); + } + cache.put(quark, element); } elements.add(element); } @@ -225,15 +228,20 @@ public class InstrumentedCallStackElement extends CallStackElement { */ public CallStack getCallStack() { CallStack callstack = fCallstack; + List<Integer> subAttributes = getStackQuarks(); if (callstack == null) { IHostIdProvider hostProvider = fHostResolver.apply(this); IThreadIdResolver threadIdResolver = fThreadIdResolver; IThreadIdProvider threadIdProvider = threadIdResolver == null ? null : threadIdResolver.resolve(hostProvider, this); - List<Integer> subAttributes = getStackQuarks(); callstack = new CallStack(getStateSystem(), subAttributes, this, hostProvider, threadIdProvider); fCallstack = callstack; + } else { + // Update the callstack if attributes were added + if (callstack.getMaxDepth() < subAttributes.size() ) { + callstack.updateAttributes(subAttributes); + } } - return callstack; + return Objects.requireNonNull(callstack); } /** diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/callgraph/CallGraphAnalysis.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/callgraph/CallGraphAnalysis.java index 8ea681768..da4851f3f 100644 --- a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/callgraph/CallGraphAnalysis.java +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/callgraph/CallGraphAnalysis.java @@ -209,7 +209,7 @@ public class CallGraphAnalysis extends TmfAbstractAnalysisModule implements ICal iterateOverCallstack(element, callStack, nextFunction, 2, aggregatedChild, model, start, end, monitor); aggregatedChild.addFunctionCall(nextFunction); // Add the kernel statuses if available - Iterable<ProcessStatusInterval> kernelStatuses = callStack.getKernelStatuses(nextFunction, -1); + Iterable<ProcessStatusInterval> kernelStatuses = callStack.getKernelStatuses(nextFunction, Collections.emptyList()); for (ProcessStatusInterval status : kernelStatuses) { aggregatedChild.addKernelStatus(status); } diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartDataProvider.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartDataProvider.java new file mode 100644 index 000000000..885efab8e --- /dev/null +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartDataProvider.java @@ -0,0 +1,740 @@ +/******************************************************************************* + * Copyright (c) 2018 É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.incubator.internal.callstack.core.instrumented.provider; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.analysis.os.linux.core.model.HostThread; +import org.eclipse.tracecompass.common.core.log.TraceCompassLog; +import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils.FlowScopeLog; +import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils.FlowScopeLogBuilder; +import org.eclipse.tracecompass.incubator.callstack.core.base.ICallStackElement; +import org.eclipse.tracecompass.incubator.callstack.core.flamechart.CallStack; +import org.eclipse.tracecompass.incubator.callstack.core.instrumented.CallStackDepth; +import org.eclipse.tracecompass.incubator.callstack.core.instrumented.ICalledFunction; +import org.eclipse.tracecompass.incubator.callstack.core.instrumented.IFlameChartProvider; +import org.eclipse.tracecompass.incubator.callstack.core.instrumented.statesystem.CallStackSeries; +import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.InstrumentedCallStackElement; +import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartEntryModel.EntryType; +import org.eclipse.tracecompass.internal.analysis.os.linux.core.threadstatus.ThreadEntryModel; +import org.eclipse.tracecompass.internal.analysis.os.linux.core.threadstatus.ThreadStatusDataProvider; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.AbstractTmfTraceDataProvider; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.CommonStatusMessage; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.SelectionTimeQueryFilter; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.TimeQueryFilter; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.ITimeGraphArrow; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.ITimeGraphDataProvider; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.ITimeGraphRowModel; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.ITimeGraphState; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.TimeGraphRowModel; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.TimeGraphState; +import org.eclipse.tracecompass.internal.provisional.tmf.core.response.ITmfResponse; +import org.eclipse.tracecompass.internal.provisional.tmf.core.response.ITmfResponse.Status; +import org.eclipse.tracecompass.internal.provisional.tmf.core.response.TmfModelResponse; +import org.eclipse.tracecompass.segmentstore.core.ISegment; +import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException; +import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; +import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; +import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderManager; +import org.eclipse.tracecompass.tmf.core.symbols.ISymbolProvider; +import org.eclipse.tracecompass.tmf.core.symbols.SymbolProviderManager; +import org.eclipse.tracecompass.tmf.core.symbols.SymbolProviderUtils; +import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; +import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; +import org.eclipse.tracecompass.tmf.core.util.Pair; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; + +/** + * This class provides the data from an instrumented callstack analysis, in the + * form of a flamechart, ie the groups are returned hierarchically and leaf + * groups return their callstacks. + * + * @author Geneviève Bastien + */ +@SuppressWarnings("restriction") +public class FlameChartDataProvider extends AbstractTmfTraceDataProvider implements ITimeGraphDataProvider<FlameChartEntryModel> { + + /** + * Provider ID. + */ + public static final String ID = "org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.flamechart"; //$NON-NLS-1$ + private static final AtomicLong ENTRY_ID = new AtomicLong(); + /** + * Logger for Abstract Tree Data Providers. + */ + private static final Logger LOGGER = TraceCompassLog.getLogger(FlameChartDataProvider.class); + + private final Map<Long, FlameChartEntryModel> fEntries = new HashMap<>(); + // Key is the row ID that requires linked data (for instance a kernel row) and + // value is the row being linked to (the one from the callstack) + private final BiMap<Long, Long> fLinkedEntries = HashBiMap.create(); + private final Collection<ISymbolProvider> fProviders = new ArrayList<>(); + private final BiMap<Long, CallStackDepth> fIdToCallstack = HashBiMap.create(); + private final BiMap<Long, ICallStackElement> fIdToElement = HashBiMap.create(); + private final long fTraceId = ENTRY_ID.getAndIncrement(); + + private static class TidInformation { + private final HostThread fTid; + private final long fStart; + private final long fEnd; + private final Long fLinked; + + public TidInformation(HostThread hostThread, long start, long end, Long linked) { + fTid = hostThread; + fStart = start; + fEnd = end; + fLinked = linked; + } + + public boolean intersects(ITimeGraphState state) { + return !(state.getStartTime() > fEnd || (state.getStartTime() + state.getDuration()) < fStart); + } + + public boolean precedes(ITimeGraphState state) { + return (state.getStartTime() + state.getDuration() < fEnd); + } + + public ITimeGraphState sanitize(ITimeGraphState state) { + if (state.getStartTime() < fStart || state.getStartTime() + state.getDuration() > fEnd) { + long start = Math.max(state.getStartTime(), fStart); + long end = Math.min(state.getStartTime() + state.getDuration(), fEnd); + String label = state.getLabel(); + if (label != null) { + return new TimeGraphState(start, end - start, state.getValue(), label); + } + return new TimeGraphState(start, end - start, state.getValue()); + } + return state; + } + } + + private static class ThreadData { + + private final ThreadStatusDataProvider fThreadDataProvider; + private final List<ThreadEntryModel> fThreadTree; + private final Status fStatus; + + public ThreadData(ThreadStatusDataProvider dataProvider, List<ThreadEntryModel> threadTree, Status status) { + fThreadDataProvider = dataProvider; + fThreadTree = threadTree; + fStatus = status; + } + + public @Nullable Map<String, String> fetchTooltip(int threadId, long time, @Nullable IProgressMonitor monitor) { + for (ThreadEntryModel entry : fThreadTree) { + if (entry.getThreadId() == threadId && entry.getStartTime() <= time && entry.getEndTime() >= time) { + TmfModelResponse<Map<String, String>> tooltip = fThreadDataProvider.fetchTooltip(new SelectionTimeQueryFilter(Collections.singletonList(time), Collections.singleton(entry.getId())), monitor); + return tooltip.getModel(); + } + } + return null; + } + + } + + private final LoadingCache<Pair<Integer, ICalledFunction>, @Nullable String> fTimeEventNames = Objects.requireNonNull(CacheBuilder.newBuilder() + .maximumSize(1000) + .build(new CacheLoader<Pair<Integer, ICalledFunction>, @Nullable String>() { + @Override + public @Nullable String load(Pair<Integer, ICalledFunction> pidInterval) { + Integer pid = pidInterval.getFirst(); + ICalledFunction interval = pidInterval.getSecond(); + + Object nameValue = interval.getSymbol(); + Long address = null; + String name = null; + if (nameValue instanceof String) { + name = (String) nameValue; + try { + address = Long.parseLong(name, 16); + } catch (NumberFormatException e) { + // leave name as null + } + } else if (nameValue instanceof Integer) { + Integer intValue = (Integer) nameValue; + name = "0x" + Integer.toUnsignedString(intValue, 16); //$NON-NLS-1$ + address = intValue.longValue(); + } else if (nameValue instanceof Long) { + address = (long) nameValue; + name = "0x" + Long.toUnsignedString(address, 16); //$NON-NLS-1$ + } + if (address != null) { + name = SymbolProviderUtils.getSymbolText(fProviders, pid, interval.getStart(), address); + } + return name; + } + })); + + private final IFlameChartProvider fFcProvider; + + private final String fAnalysisId; + private final ReentrantReadWriteLock fLock = new ReentrantReadWriteLock(false); + private @Nullable TmfModelResponse<List<FlameChartEntryModel>> fCached; + private @Nullable ThreadData fThreadData = null; + + /** + * Constructor + * + * @param trace + * The trace for which this data provider applies + * @param module + * The flame chart provider encapsulated by this provider + * @param secondaryId + * The ID of the flame chart provider + */ + public FlameChartDataProvider(ITmfTrace trace, IFlameChartProvider module, String secondaryId) { + super(trace); + fFcProvider = module; + fAnalysisId = secondaryId; + resetFunctionNames(new NullProgressMonitor()); + } + + @Override + public TmfModelResponse<List<ITimeGraphArrow>> fetchArrows(TimeQueryFilter filter, @Nullable IProgressMonitor monitor) { + // TODO Implement + return new TmfModelResponse<>(null, Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + + @Override + public TmfModelResponse<Map<String, String>> fetchTooltip(SelectionTimeQueryFilter filter, @Nullable IProgressMonitor monitor) { + try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameChartDataProvider#fetchTooltip") //$NON-NLS-1$ + .setCategory(getClass().getSimpleName()).build()) { + Map<Long, FlameChartEntryModel> entries = getSelectedEntries(filter); + if (entries.size() != 1) { + // Not the expected size of tooltip, just return empty + return new TmfModelResponse<>(null, Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + Entry<@NonNull Long, @NonNull FlameChartEntryModel> entry = entries.entrySet().iterator().next(); + Map<String, String> tooltip = getTooltip(entry, filter, monitor); + + return new TmfModelResponse<>(tooltip, Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + } + + private @Nullable Map<String, String> getTooltip(Entry<Long, FlameChartEntryModel> entry, SelectionTimeQueryFilter filter, @Nullable IProgressMonitor monitor) { + FlameChartEntryModel value = Objects.requireNonNull(entry.getValue()); + switch (value.getEntryType()) { + case FUNCTION: + { + CallStackDepth selectedDepth = fIdToCallstack.get(entry.getKey()); + if (selectedDepth == null) { + return null; + } + Multimap<CallStackDepth, ISegment> csFunctions = fFcProvider.queryCallStacks(Collections.singleton(selectedDepth), Collections.singleton(filter.getStart())); + Collection<ISegment> functions = csFunctions.get(selectedDepth); + if (functions.isEmpty()) { + return null; + } + ISegment next = functions.iterator().next(); + if (!(next instanceof ICalledFunction)) { + return null; + } + ICalledFunction currentFct = (ICalledFunction) next; + Map<String, String> tooltips = new HashMap<>(); + int threadId = currentFct.getThreadId(); + if (threadId > 0) { + tooltips.put(String.valueOf(Messages.FlameChartDataProvider_ThreadId), String.valueOf(threadId)); + } + // TODO: Add symbol origin (library, language, etc) when better supported + return tooltips; + } + case KERNEL: + // Get the tooltip from the the ThreadStatusDataProvider + // First get the linked function to know which TID to retrieve + Long csId = fLinkedEntries.get(entry.getKey()); + if (csId == null) { + return null; + } + CallStackDepth selectedDepth = fIdToCallstack.get(csId); + if (selectedDepth == null) { + return null; + } + int threadId = selectedDepth.getCallStack().getThreadId(filter.getStart()); + ThreadData threadData = fThreadData; + if (threadData == null) { + return null; + } + return threadData.fetchTooltip(threadId, filter.getStart(), monitor); + case LEVEL: + // Fall-through + case TRACE: + // Fall-through + default: + return null; + } + + } + + @Override + public String getId() { + return ID + ':' + fAnalysisId; + } + + // Get an entry for a quark + private long getEntryId(CallStackDepth stack) { + return fIdToCallstack.inverse().computeIfAbsent(stack, q -> ENTRY_ID.getAndIncrement()); + } + + private long getEntryId(ICallStackElement instrumentedCallStackElement) { + return fIdToElement.inverse().computeIfAbsent(instrumentedCallStackElement, q -> ENTRY_ID.getAndIncrement()); + } + + // Get a new entry for a kernel entry ID + private long getKernelEntryId(long baseId) { + return fLinkedEntries.inverse().computeIfAbsent(baseId, id -> ENTRY_ID.getAndIncrement()); + } + + @Override + public TmfModelResponse<List<FlameChartEntryModel>> fetchTree(TimeQueryFilter filter, @Nullable IProgressMonitor monitor) { + if (fCached != null) { + return fCached; + } + + fLock.writeLock().lock(); + try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameChartDataProvider#fetchTree") //$NON-NLS-1$ + .setCategory(getClass().getSimpleName()).build()) { + IFlameChartProvider fcProvider = fFcProvider; + boolean complete = fcProvider.isComplete(); + CallStackSeries callstack = fcProvider.getCallStackSeries(); + if (callstack == null) { + return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.ANALYSIS_INITIALIZATION_FAILED); + } + long start = getTrace().getStartTime().getValue(); + long end = getTrace().readEnd().getValue(); + + // Initialize the first element of the tree + ImmutableList.Builder<FlameChartEntryModel> builder = ImmutableList.builder(); + FlameChartEntryModel traceEntry = new FlameChartEntryModel(fTraceId, -1, getTrace().getName(), start, end, FlameChartEntryModel.EntryType.TRACE); + builder.add(traceEntry); + + FlameChartEntryModel callStackRoot = traceEntry; + // If there is more than one callstack objects in the analysis, create a root + // per series + boolean needsKernel = false; + for (ICallStackElement element : callstack.getRootElements()) { + if (monitor != null && monitor.isCanceled()) { + return new TmfModelResponse<>(null, ITmfResponse.Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED); + } + needsKernel |= processCallStackElement(element, builder, callStackRoot); + } + // Initialize the thread status data provider + if (needsKernel) { + prepareKernelData(monitor, start); + } + List<FlameChartEntryModel> tree = builder.build(); + tree.forEach(entry -> fEntries.put(entry.getId(), entry)); + if (complete) { + TmfModelResponse<List<FlameChartEntryModel>> response = new TmfModelResponse<>(tree, + ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + fCached = response; + return response; + } + return new TmfModelResponse<>(tree, ITmfResponse.Status.RUNNING, CommonStatusMessage.RUNNING); + } finally { + fLock.writeLock().unlock(); + } + } + + private void prepareKernelData(@Nullable IProgressMonitor monitor, long start) { + ThreadData data = fThreadData; + if (data != null && data.fStatus.equals(Status.COMPLETED)) { + return; + } + // FIXME: Wouldn't work correctly if trace is an experiment as it would cover many hosts + Set<ITmfTrace> tracesForHost = TmfTraceManager.getInstance().getTracesForHost(getTrace().getHostId()); + for (ITmfTrace trace : tracesForHost) { + ThreadStatusDataProvider dataProvider = DataProviderManager.getInstance().getDataProvider(trace, ThreadStatusDataProvider.ID, ThreadStatusDataProvider.class); + if (dataProvider != null) { + // Get the tree for the trace's current range + TmfModelResponse<List<ThreadEntryModel>> threadTreeResp = dataProvider.fetchTree(new TimeQueryFilter(start, Long.MAX_VALUE, 2), monitor); + List<ThreadEntryModel> threadTree = threadTreeResp.getModel(); + if (threadTree != null) { + fThreadData = new ThreadData(dataProvider, threadTree, threadTreeResp.getStatus()); + break; + } + } + } + } + + private boolean processCallStackElement(ICallStackElement element, Builder<FlameChartEntryModel> builder, FlameChartEntryModel parentEntry) { + + long elementId = getEntryId(element); + FlameChartEntryModel entry = new FlameChartEntryModel(elementId, parentEntry.getId(), element.getName(), parentEntry.getStartTime(), parentEntry.getEndTime(), FlameChartEntryModel.EntryType.LEVEL); + builder.add(entry); + + boolean needsKernel = false; + + // Is this an intermediate or leaf element + if ((element instanceof InstrumentedCallStackElement) && element.isLeaf()) { + // For the leaf element, add the callstack entries + InstrumentedCallStackElement finalElement = (InstrumentedCallStackElement) element; + CallStack callStack = finalElement.getCallStack(); + for (int depth = 0; depth < callStack.getMaxDepth(); depth++) { + FlameChartEntryModel flameChartEntry = new FlameChartEntryModel(getEntryId(new CallStackDepth(callStack, depth + 1)), entry.getId(), element.getName(), parentEntry.getStartTime(), parentEntry.getEndTime(), + FlameChartEntryModel.EntryType.FUNCTION, depth + 1); + builder.add(flameChartEntry); + if (depth == 0 && callStack.hasKernelStatuses()) { + needsKernel = true; + builder.add(new FlameChartEntryModel(getKernelEntryId(flameChartEntry.getId()), entry.getId(), String.valueOf(Messages.FlameChartDataProvider_KernelStatusTitle), parentEntry.getStartTime(), parentEntry.getEndTime(), FlameChartEntryModel.EntryType.KERNEL)); + } + } + return needsKernel; + } + // Intermediate element, process children + for (ICallStackElement child : element.getChildren()) { + needsKernel |= processCallStackElement(child, builder, entry); + } + return needsKernel; + } + + // Get the selected entries with the quark + private BiMap<Long, FlameChartEntryModel> getSelectedEntries(SelectionTimeQueryFilter filter) { + fLock.readLock().lock(); + try { + BiMap<Long, FlameChartEntryModel> selectedEntries = HashBiMap.create(); + + for (Long selectedItem : filter.getSelectedItems()) { + FlameChartEntryModel entryModel = fEntries.get(selectedItem); + if (entryModel != null) { + selectedEntries.put(selectedItem, entryModel); + } + } + return selectedEntries; + } finally { + fLock.readLock().unlock(); + } + } + + @Override + public TmfModelResponse<List<ITimeGraphRowModel>> fetchRowModel(SelectionTimeQueryFilter filter, @Nullable IProgressMonitor monitor) { + try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameChartDataProvider#fetchRowModel") //$NON-NLS-1$ + .setCategory(getClass().getSimpleName()).build()) { + + Map<Long, FlameChartEntryModel> entries = getSelectedEntries(filter); + if (entries.size() == 1 && filter.getTimesRequested().length == 2) { + // this is a request for a follow event. + Entry<@NonNull Long, @NonNull FlameChartEntryModel> entry = entries.entrySet().iterator().next(); + if (filter.getStart() == Long.MIN_VALUE) { + return new TmfModelResponse<>(getFollowEvent(entry, filter.getEnd(), false), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + } else if (filter.getEnd() == Long.MAX_VALUE) { + return new TmfModelResponse<>(getFollowEvent(entry, filter.getStart(), true), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + } + // For each kernel status entry, add the first row of the callstack + addRequiredCallstacks(entries); + + SubMonitor subMonitor = SubMonitor.convert(monitor, "FlameChartDataProvider#fetchRowModel", 2); //$NON-NLS-1$ + IFlameChartProvider fcProvider = fFcProvider; + boolean complete = fcProvider.isComplete(); + + Map<Long, List<ITimeGraphState>> csRows = getCallStackRows(filter, entries, subMonitor); + if (csRows == null) { + // getRowModel returns null if the query was cancelled. + return new TmfModelResponse<>(null, ITmfResponse.Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED); + } + List<ITimeGraphRowModel> collect = csRows.entrySet().stream().map(entry -> new TimeGraphRowModel(entry.getKey(), entry.getValue())).collect(Collectors.toList()); + return new TmfModelResponse<>(collect, complete ? Status.COMPLETED : Status.RUNNING, + complete ? CommonStatusMessage.COMPLETED : CommonStatusMessage.RUNNING); + } catch (IndexOutOfBoundsException | TimeRangeException | StateSystemDisposedException e) { + return new TmfModelResponse<>(null, Status.FAILED, String.valueOf(e.getMessage())); + } + } + + private void addRequiredCallstacks(Map<Long, FlameChartEntryModel> entries) { + fLock.readLock().lock(); + try { + Map<Long, FlameChartEntryModel> toAdd = new HashMap<>(); + for (Long id : entries.keySet()) { + Long csId = fLinkedEntries.get(id); + if (csId != null) { + FlameChartEntryModel entry = fEntries.get(csId); + if (entry != null) { + toAdd.put(csId, entry); + } + } + } + entries.putAll(toAdd); + } finally { + fLock.readLock().unlock(); + } + } + + private @Nullable Map<Long, List<ITimeGraphState>> getCallStackRows(SelectionTimeQueryFilter filter, Map<Long, FlameChartEntryModel> entries, SubMonitor subMonitor) throws IndexOutOfBoundsException, TimeRangeException, StateSystemDisposedException { + + // Get the data for the model entries that are of type function + Map<Long, List<ITimeGraphState>> rows = new HashMap<>(); + List<TidInformation> tids = new ArrayList<>(); + Map<Long, CallStackDepth> csEntries = new HashMap<>(); + for (Entry<Long, @NonNull FlameChartEntryModel> entry : entries.entrySet()) { + CallStackDepth selectedDepth = fIdToCallstack.get(entry.getKey()); + if (selectedDepth != null && entry.getValue().getEntryType().equals(EntryType.FUNCTION)) { + csEntries.put(entry.getKey(), selectedDepth); + } + } + + long[] timesRequested = filter.getTimesRequested(); + // Prepare the list of times + List<Long> times = new ArrayList<>(); + for (long time : timesRequested) { + times.add(time); + } + Collections.sort(times); + Multimap<CallStackDepth, ISegment> csFunctions = fFcProvider.queryCallStacks(csEntries.values(), times); + + for (Map.Entry<Long, CallStackDepth> entry : csEntries.entrySet()) { + if (subMonitor.isCanceled()) { + return null; + } + Collection<ISegment> states = csFunctions.get(entry.getValue()); + + // Create the time graph states for this row + List<ITimeGraphState> eventList = new ArrayList<>(states.size()); + states.forEach(state -> eventList.add(createTimeGraphState(state))); + eventList.sort(Comparator.comparingLong(ITimeGraphState::getStartTime)); + rows.put(entry.getKey(), eventList); + + // See if any more row needs to be filled with these function's data + // TODO: Kernel might not be the only type of linked entries (for instance, + // locations of sampling data) + Long linked = fLinkedEntries.inverse().get(entry.getKey()); + if (linked == null || !entries.containsKey(linked)) { + continue; + } + tids.addAll(getKernelTids(entry.getValue(), states, linked)); + + } + // Add an empty state to rows that do not have data + for (Long key : entries.keySet()) { + if (!rows.containsKey(key)) { + rows.put(key, Collections.emptyList()); + } + } + if (!tids.isEmpty()) { + rows.putAll(getKernelStates(tids, times, subMonitor)); + } + subMonitor.worked(1); + return rows; + } + + private Map<Long, List<ITimeGraphState>> getKernelStates(List<TidInformation> tids, List<Long> times, SubMonitor monitor) { + // Get the thread statuses from the thread status provider + ThreadData threadData = fThreadData; + if (threadData == null) { + return Collections.emptyMap(); + + } + List<ThreadEntryModel> tree = threadData.fThreadTree; + + // FIXME: A callstack analysis may be for an experiment that span many hosts, + // the thread data provider will be a composite and the models may be for + // different host IDs. But for now, suppose the callstack is a composite also + // and the trace filtered the right host. + BiMap<Long, Integer> threadModelIds = filterThreads(tree, tids); + SelectionTimeQueryFilter tidFilter = new SelectionTimeQueryFilter(times, threadModelIds.keySet()); + TmfModelResponse<List<ITimeGraphRowModel>> rowModel = threadData.fThreadDataProvider.fetchRowModel(tidFilter, monitor); + List<ITimeGraphRowModel> rowModels = rowModel.getModel(); + if (rowModel.getStatus().equals(Status.CANCELLED) || rowModel.getStatus().equals(Status.FAILED) || rowModels == null) { + return Collections.emptyMap(); + } + return mapThreadStates(rowModels, threadModelIds, tids); + } + + private static Map<Long, List<ITimeGraphState>> mapThreadStates(List<ITimeGraphRowModel> rowModels, BiMap<Long, Integer> threadModelIds, List<TidInformation> tids) { + ImmutableMap<Long, ITimeGraphRowModel> statusRows = Maps.uniqueIndex(rowModels, m -> m.getEntryID()); + // Match the states of thread status to the requested tid lines + Long prevId = -1L; + List<ITimeGraphState> states = null; + Map<Long, List<ITimeGraphState>> kernelStatuses = new HashMap<>(); + // The tid information data are ordered by id and times + for (TidInformation tidInfo : tids) { + Long tidEntryId = threadModelIds.inverse().get(tidInfo.fTid.getTid()); + if (tidEntryId == null) { + continue; + } + ITimeGraphRowModel rowModel = statusRows.get(tidEntryId); + if (tidInfo.fLinked != prevId || states == null) { + if (states != null) { + kernelStatuses.put(prevId, states); + } + states = new ArrayList<>(); + } + rowModel.getStates(); + for (ITimeGraphState state : rowModel.getStates()) { + if (tidInfo.intersects(state)) { + states.add(tidInfo.sanitize(state)); + } + if (!tidInfo.precedes(state)) { + break; + } + } + prevId = tidInfo.fLinked; + } + if (states != null) { + kernelStatuses.put(prevId, states); + } + return kernelStatuses; + } + + private static BiMap<Long, Integer> filterThreads(List<ThreadEntryModel> model, List<TidInformation> tids) { + // Get the entry model IDs that match requested tids + BiMap<Long, Integer> tidEntries = HashBiMap.create(); + Set<Integer> selectedTids = new HashSet<>(); + for (TidInformation tidInfo : tids) { + selectedTids.add(tidInfo.fTid.getTid()); + } + for (ThreadEntryModel entryModel : model) { + if (selectedTids.contains(entryModel.getThreadId())) { + try { + tidEntries.put(entryModel.getId(), entryModel.getThreadId()); + } catch (IllegalArgumentException e) { + // FIXME: There may be many entries for one tid, don't rely on exception for + // real workflow. Works for now. + } + } + } + return tidEntries; + } + + private static Collection<TidInformation> getKernelTids(CallStackDepth callStackDepth, Collection<ISegment> states, Long linked) { + + List<TidInformation> tids = new ArrayList<>(); + CallStack callStack = callStackDepth.getCallStack(); + if (!callStack.isTidVariable()) { + // Find the time of the first function to know which timestamp to query + HostThread hostThread = callStack.getHostThread(); + if (hostThread != null) { + tids.add(new TidInformation(hostThread, Long.MIN_VALUE, Long.MAX_VALUE, linked)); + } + return tids; + } + // Get the thread IDs for all functions + for (ISegment state : states) { + if (!(state instanceof ICalledFunction)) { + continue; + } + ICalledFunction function = (ICalledFunction) state; + HostThread hostThread = callStack.getHostThread(function.getStart()); + if (hostThread != null) { + tids.add(new TidInformation(hostThread, function.getStart(), function.getEnd(), linked)); + } + } + + return tids; + } + + private ITimeGraphState createTimeGraphState(ISegment state) { + if (!(state instanceof ICalledFunction)) { + return new TimeGraphState(state.getStart(), state.getLength(), Integer.MIN_VALUE); + } + ICalledFunction function = (ICalledFunction) state; + Object value = function.getSymbol(); + Integer pid = function.getProcessId(); + String name = String.valueOf(fTimeEventNames.getUnchecked(new Pair<>(pid, function))); + return new TimeGraphState(function.getStart(), function.getLength(), value.hashCode(), name); + } + + /** + * Invalidate the function names cache and load the symbol providers. This + * function should be used at the beginning of the provider, or whenever new + * symbol providers are added + * + * @param monitor + * A progress monitor to follow this operation + */ + public void resetFunctionNames(IProgressMonitor monitor) { + fTimeEventNames.invalidateAll(); + synchronized (fProviders) { + Collection<@NonNull ISymbolProvider> symbolProviders = SymbolProviderManager.getInstance().getSymbolProviders(getTrace()); + SubMonitor sub = SubMonitor.convert(monitor, "CallStackDataProvider#resetFunctionNames", symbolProviders.size()); //$NON-NLS-1$ + fProviders.clear(); + for (ISymbolProvider symbolProvider : symbolProviders) { + fProviders.add(symbolProvider); + symbolProvider.loadConfiguration(sub); + sub.worked(1); + } + } + } + + /** + * Get the next or previous interval for a call stack entry ID, time and + * direction + * + * @param entry + * whose key is the ID and value is the quark for the entry whose + * next / previous state we are searching for + * @param time + * selection start time + * @param forward + * if going to next or previous + * @return the next / previous state encapsulated in a row if it exists, else + * null + */ + private @Nullable List<ITimeGraphRowModel> getFollowEvent(Entry<Long, FlameChartEntryModel> entry, long time, boolean forward) { + FlameChartEntryModel value = Objects.requireNonNull(entry.getValue()); + switch (value.getEntryType()) { + case FUNCTION: + CallStackDepth selectedDepth = fIdToCallstack.get(entry.getKey()); + if (selectedDepth == null) { + return null; + } + // Ask the callstack the depth at the current time + ITmfStateInterval nextDepth = selectedDepth.getCallStack().getNextDepth(time, forward); + if (nextDepth == null) { + return null; + } + Object depthVal = nextDepth.getValue(); + int depth = (depthVal instanceof Number) ? ((Number) depthVal).intValue() : 0; + TimeGraphState state = new TimeGraphState(nextDepth.getStartTime(), nextDepth.getEndTime() - nextDepth.getStartTime(), depth); + TimeGraphRowModel row = new TimeGraphRowModel(entry.getKey(), Collections.singletonList(state)); + return Collections.singletonList(row); + + case KERNEL: + break; + case LEVEL: + // Fall-through + case TRACE: + // Fall-through + default: + return null; + } + return null; + } + +} diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartDataProviderFactory.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartDataProviderFactory.java new file mode 100644 index 000000000..c2cab0169 --- /dev/null +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartDataProviderFactory.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2018 É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.incubator.internal.callstack.core.instrumented.provider; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.incubator.callstack.core.instrumented.IFlameChartProvider; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.tree.ITmfTreeDataModel; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.tree.ITmfTreeDataProvider; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.xy.TmfTreeXYCompositeDataProvider; +import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule; +import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderFactory; +import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; +import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; +import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils; + +import com.google.common.collect.Iterables; + +/** + * Factory for the flame chart data provider + * + * @author Geneviève Bastien + */ +public class FlameChartDataProviderFactory implements IDataProviderFactory { + + @Override + public @Nullable ITmfTreeDataProvider<? extends ITmfTreeDataModel> createProvider(ITmfTrace trace) { + // Need the analysis + return null; + } + + @Override + public @Nullable ITmfTreeDataProvider<? extends ITmfTreeDataModel> createProvider(ITmfTrace trace, String secondaryId) { + // Create with the trace or experiment first + ITmfTreeDataProvider<? extends ITmfTreeDataModel> provider = create(trace, secondaryId); + if (provider != null) { + return provider; + } + // Otherwise, see if it's an experiment and create a composite if that's the + // case + Collection<ITmfTrace> traces = TmfTraceManager.getTraceSet(trace); + if (traces.size() > 1) { + // Try creating a composite only if there are many traces, otherwise, the + // previous call to create should have returned the data provider + return TmfTreeXYCompositeDataProvider.create(traces, Objects.requireNonNull("FlameChart"), FlameChartDataProvider.ID, secondaryId); + } + return null; + + } + + private static @Nullable ITmfTreeDataProvider<? extends ITmfTreeDataModel> create(ITmfTrace trace, String secondaryId) { + // The trace can be an experiment, so we need to know if there are multiple + // analysis modules with the same ID + Iterable<IFlameChartProvider> modules = TmfTraceUtils.getAnalysisModulesOfClass(trace, IFlameChartProvider.class); + Iterable<IFlameChartProvider> filteredModules = Iterables.filter(modules, m -> ((IAnalysisModule) m).getId().equals(secondaryId)); + Iterator<IFlameChartProvider> iterator = filteredModules.iterator(); + if (iterator.hasNext()) { + IFlameChartProvider module = iterator.next(); + if (iterator.hasNext()) { + // More than one module, must be an experiment, return null so the factory can + // try with individual traces + return null; + } + ((IAnalysisModule) module).schedule(); + return new FlameChartDataProvider(trace, module, secondaryId); + } + return null; + } + +} diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartEntryModel.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartEntryModel.java new file mode 100644 index 000000000..c82af1740 --- /dev/null +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartEntryModel.java @@ -0,0 +1,138 @@ +/********************************************************************** + * Copyright (c) 2018 É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.incubator.internal.callstack.core.instrumented.provider; + +import java.util.Objects; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.TimeGraphEntryModel; + +/** + * {@link TimeGraphEntryModel} for the Flame chart data + * + * @author Geneviève Bastien + */ +@SuppressWarnings("restriction") +public class FlameChartEntryModel extends TimeGraphEntryModel { + + /** + * An enumeration for the type of flame chart entries + */ + public enum EntryType { + /** + * A descriptive entry, for example the one for the trace + */ + TRACE, + /** + * Represent a group of the callstack analysis + */ + LEVEL, + /** + * Represent an entry with function data, the actual callstack data + */ + FUNCTION, + /** + * This entry will show the kernel statuses for the TID running the callstack. + * Will not always be present + */ + KERNEL + } + + private final EntryType fEntryType; + private final int fDepth; + + /** + * Constructor + * + * @param id + * unique ID for this {@link FlameChartEntryModel} + * @param parentId + * parent's ID to build the tree + * @param name + * entry's name + * @param startTime + * entry's start time + * @param endTime + * entry's end time + * @param entryType + * The type of this entry + */ + public FlameChartEntryModel(long id, long parentId, String name, long startTime, long endTime, EntryType entryType) { + super(id, parentId, name, startTime, endTime); + fEntryType = entryType; + fDepth = -1; + } + + /** + * Constructor + * + * @param elementId + * unique ID for this {@link FlameChartEntryModel} + * @param parentId + * parent's ID to build the tree + * @param name + * entry's name + * @param startTime + * entry's start time + * @param endTime + * entry's end time + * @param entryType + * The type of this entry + * @param depth + * entry's PID or TID if is a thread + */ + public FlameChartEntryModel(long elementId, long parentId, String name, long startTime, long endTime, EntryType entryType, int depth) { + super(elementId, parentId, name, startTime, endTime); + fEntryType = entryType; + fDepth = depth; + } + + /** + * Getter for the entry type + * + * @return The type of entry. + */ + public EntryType getEntryType() { + return fEntryType; + } + + /** + * Get the depth of this entry + * + * @return The depth of this entry + */ + public int getDepth() { + return fDepth; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!super.equals(obj)) { + // nullness, class, name, ids + return false; + } + if (!(obj instanceof FlameChartEntryModel)) { + return false; + } + FlameChartEntryModel other = (FlameChartEntryModel) obj; + return fEntryType == other.fEntryType; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), fEntryType); + } + + @Override + public String toString() { + return super.toString() + ' ' + fEntryType.toString(); + } + +} diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/Messages.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/Messages.java new file mode 100644 index 000000000..8d77f2c3c --- /dev/null +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/Messages.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2018 É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.incubator.internal.callstack.core.instrumented.provider; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.osgi.util.NLS; + +/** + * Message bundle for the call stack state provider. + */ +public class Messages extends NLS { + + private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$ + + /** Title of the thread ID tooltip key */ + public static @Nullable String FlameChartDataProvider_ThreadId = null; + + /** Title of kernel status rows */ + public static @Nullable String FlameChartDataProvider_KernelStatusTitle; + + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } + +} diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/messages.properties b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/messages.properties new file mode 100644 index 000000000..cfc5acd59 --- /dev/null +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/messages.properties @@ -0,0 +1,11 @@ +############################################################################### +# Copyright (c) 2018 É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 +############################################################################### + +FlameChartDataProvider_KernelStatusTitle=Kernel statuses +FlameChartDataProvider_ThreadId=TID diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/package-info.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/package-info.java new file mode 100644 index 000000000..c3da9b075 --- /dev/null +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/package-info.java @@ -0,0 +1,11 @@ +/******************************************************************************* + * Copyright (c) 2018 É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.incubator.internal.callstack.core.instrumented.provider; diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/xml/callstack/CallstackXmlAnalysis.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/xml/callstack/CallstackXmlAnalysis.java index fc28f1d0b..b640e21a4 100644 --- a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/xml/callstack/CallstackXmlAnalysis.java +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/xml/callstack/CallstackXmlAnalysis.java @@ -359,4 +359,17 @@ public class CallstackXmlAnalysis extends TmfAbstractAnalysisModule implements I } } + @Override + public boolean isComplete() { + // Initialization error, but the analysis is completed + if (!waitForInitialization()) { + return true; + } + Iterator<ITmfStateSystem> iterator = getStateSystems().iterator(); + if (!iterator.hasNext()) { + throw new IllegalStateException("The initialization is complete, so the state system must not be null"); //$NON-NLS-1$ + } + return iterator.next().waitUntilBuilt(0); + } + } diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.ui/src/org/eclipse/tracecompass/incubator/internal/callstack/ui/views/flamechart/ProcessStatusEntry.java b/callstack/org.eclipse.tracecompass.incubator.callstack.ui/src/org/eclipse/tracecompass/incubator/internal/callstack/ui/views/flamechart/ProcessStatusEntry.java index 9b29b6acb..e69c5dc6b 100644 --- a/callstack/org.eclipse.tracecompass.incubator.callstack.ui/src/org/eclipse/tracecompass/incubator/internal/callstack/ui/views/flamechart/ProcessStatusEntry.java +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.ui/src/org/eclipse/tracecompass/incubator/internal/callstack/ui/views/flamechart/ProcessStatusEntry.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tracecompass.incubator.analysis.core.concepts.ProcessStatusInterval; import org.eclipse.tracecompass.incubator.callstack.core.flamechart.CallStack; import org.eclipse.tracecompass.incubator.callstack.core.instrumented.ICalledFunction; +import org.eclipse.tracecompass.statesystem.core.StateSystemUtils; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeEvent; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry; @@ -76,7 +77,7 @@ public class ProcessStatusEntry extends TimeGraphEntry { ICalledFunction function = fcEvent.getFunction(); // FIXME: This gets all the statuses, that can be big for large time ranges. Use // a method with resolution when it is available - Iterable<@NonNull ProcessStatusInterval> statuses = fCallStack.getKernelStatuses(function, resolution); + Iterable<@NonNull ProcessStatusInterval> statuses = fCallStack.getKernelStatuses(function, StateSystemUtils.getTimes(startTime, endTime, resolution)); for (ProcessStatusInterval status : statuses) { events.add(new TimeEvent(this, status.getStart(), status.getLength(), status.getProcessStatus().getStateValue().unboxInt())); } |