From 2936de9f6bed2325f84f77db42867af1521e5734 Mon Sep 17 00:00:00 2001 From: Marco Miller Date: Thu, 9 Feb 2023 08:26:36 -0500 Subject: [PATCH] callstack: Add FlameGraphDataProvider|Factory|Test -From incubator. Prepare for adding FlameGraphTest and FlameGraph{Sel}View(s) next, in turn. [Added] o.e.t.i.a.callstack.core.flamegraph.FlameGraphDataProviderFactory Change-Id: Ie4c34438286364c99815c23bfaca5218928e2f69 Signed-off-by: Marco Miller Reviewed-on: https://git.eclipse.org/r/c/tracecompass/org.eclipse.tracecompass/+/199828 Tested-by: Trace Compass Bot Tested-by: Bernd Hufmann Reviewed-by: Bernd Hufmann --- .../callgraph/FlameGraphDataProviderTest.java | 267 +++++++ .../testfiles/dp/expectedFgRowFull2Times | 11 + .../testfiles/dp/expectedFgRowFullAll | 11 + .../testfiles/dp/expectedFgRowFullZoom | 11 + .../testfiles/dp/expectedFgRowOne2Times | 3 + .../testfiles/dp/expectedFgRowOneAll | 3 + .../testfiles/dp/expectedFgRowOneZoom | 3 + .../testfiles/dp/expectedFgRowProcess2Times | 6 + .../testfiles/dp/expectedFgRowProcessAll | 6 + .../testfiles/dp/expectedFgRowProcessZoom | 6 + .../testfiles/dp/expectedFgRowSelection2Times | 10 + .../testfiles/dp/expectedFgRowSelectionAll | 10 + .../testfiles/dp/expectedFgRowSelectionZoom | 10 + .../testfiles/dp/expectedFgTreeFull | 18 + .../testfiles/dp/expectedFgTreeOne | 5 + .../testfiles/dp/expectedFgTreeProcess | 9 + .../testfiles/dp/expectedFgTreeSelection | 17 + .../META-INF/MANIFEST.MF | 1 + .../plugin.xml | 4 + .../core/flamegraph/DataProviderUtils.java | 61 ++ .../flamegraph/FlameGraphDataProvider.java | 739 ++++++++++++++++++ .../FlameGraphDataProviderFactory.java | 100 +++ .../callstack/core/flamegraph/Messages.java | 50 ++ .../core/flamegraph/messages.properties | 19 + .../core/flamegraph/package-info.java | 12 + 25 files changed, 1392 insertions(+) create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/src/org/eclipse/tracecompass/analysis/callstack/core/tests/callgraph/FlameGraphDataProviderTest.java create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowFull2Times create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowFullAll create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowFullZoom create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowOne2Times create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowOneAll create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowOneZoom create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowProcess2Times create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowProcessAll create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowProcessZoom create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowSelection2Times create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowSelectionAll create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowSelectionZoom create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeFull create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeOne create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeProcess create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeSelection create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/DataProviderUtils.java create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/FlameGraphDataProvider.java create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/FlameGraphDataProviderFactory.java create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/Messages.java create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/messages.properties create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/package-info.java diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/src/org/eclipse/tracecompass/analysis/callstack/core/tests/callgraph/FlameGraphDataProviderTest.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/src/org/eclipse/tracecompass/analysis/callstack/core/tests/callgraph/FlameGraphDataProviderTest.java new file mode 100644 index 0000000000..eba6230948 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/src/org/eclipse/tracecompass/analysis/callstack/core/tests/callgraph/FlameGraphDataProviderTest.java @@ -0,0 +1,267 @@ +/******************************************************************************* + * Copyright (c) 2019 École Polytechnique de Montréal + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package org.eclipse.tracecompass.analysis.callstack.core.tests.callgraph; + +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.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.tracecompass.analysis.callstack.core.tests.CallStackTestBase; +import org.eclipse.tracecompass.analysis.callstack.core.tests.FlameDataProviderTestUtils; +import org.eclipse.tracecompass.analysis.callstack.core.tests.stubs.CallStackAnalysisStub; +import org.eclipse.tracecompass.internal.analysis.callstack.core.flamegraph.FlameGraphDataProvider; +import org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented.FlameChartEntryModel; +import org.eclipse.tracecompass.internal.analysis.callstack.core.tree.AllGroupDescriptor; +import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils; +import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle; +import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphRowModel; +import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphState; +import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphModel; +import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeModel; +import org.eclipse.tracecompass.tmf.core.response.ITmfResponse; +import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; +import com.google.common.collect.ImmutableMap; + +/** + * Test the {@link FlameGraphDataProvider} class + * + * @author Geneviève Bastien + */ +public class FlameGraphDataProviderTest extends CallStackTestBase { + + private static final String EXPECTED_FILE_PATH = "testfiles/dp/"; + private static final String WITH_PARENT = " with parent "; + + private static final @NonNull Map<@NonNull String, @NonNull Object> TREE_PARAMETERS = ImmutableMap.of( + DataProviderParameterUtils.REQUESTED_TIME_KEY, ImmutableList.of(0, Long.MAX_VALUE)); + + // Keep the map of values to style, to make sure a same value always has the + // same style + private static final Map VALUE_TO_STYLE = new HashMap<>(); + + /** + * Test the {@link FlameGraphDataProvider} for the test callstack, with all + * items separately + * + * @throws IOException + * if an I/O error occurs reading from the expected value file + * or a malformed or unmappable byte sequence is read + */ + @Test + public void testFlameGraphDataProviderAllItems() throws IOException { + CallStackAnalysisStub cga = getModule(); + FlameGraphDataProvider provider = new FlameGraphDataProvider<>(getTrace(), cga, cga.getId()); + Map idsToNames = assertAndGetTree(provider, "expectedFgTreeFull", Collections.emptyMap()); + assertRowsRequests(provider, idsToNames, "Full", 19); + } + + /** + * Test the {@link FlameGraphDataProvider} for the test callstack, with + * items grouped by process + * + * @throws IOException + * if an I/O error occurs reading from the expected value file + * or a malformed or unmappable byte sequence is read + */ + @Test + public void testFlameGraphDataProviderGroupByProcess() throws IOException { + CallStackAnalysisStub cga = getModule(); + FlameGraphDataProvider provider = new FlameGraphDataProvider<>(getTrace(), cga, cga.getId()); + Map idsToNames = assertAndGetTree(provider, "expectedFgTreeProcess", ImmutableMap.of(FlameGraphDataProvider.GROUP_BY_KEY, "Processes/*")); + assertRowsRequests(provider, idsToNames, "Process", 38); + } + + /** + * Test the {@link FlameGraphDataProvider} for the test callstack, with all + * items grouped together. + * + * @throws IOException + * if an I/O error occurs reading from the expected value file + * or a malformed or unmappable byte sequence is read + */ + @Test + public void testFlameGraphDataProviderGrouped() throws IOException { + CallStackAnalysisStub cga = getModule(); + FlameGraphDataProvider provider = new FlameGraphDataProvider<>(getTrace(), cga, cga.getId()); + Map idsToNames = assertAndGetTree(provider, "expectedFgTreeOne", ImmutableMap.of(FlameGraphDataProvider.GROUP_BY_KEY, AllGroupDescriptor.getInstance().getName())); + assertRowsRequests(provider, idsToNames, "One", 72); + } + + /** + * Test the {@link FlameGraphDataProvider} for the test callstack, with only + * the callgraph for a time selection. + * + * @throws IOException + * if an I/O error occurs reading from the expected value file + * or a malformed or unmappable byte sequence is read + */ + @Test + public void testFlameGraphDataProviderSelection() throws IOException { + CallStackAnalysisStub cga = getModule(); + FlameGraphDataProvider provider = new FlameGraphDataProvider<>(getTrace(), cga, cga.getId()); + Map idsToNames = assertAndGetTree(provider, "expectedFgTreeSelection", ImmutableMap.of(FlameGraphDataProvider.SELECTION_RANGE_KEY, ImmutableList.of(5, 15))); + assertRowsRequests(provider, idsToNames, "Selection", 10); + } + + private static void assertRowsRequests(FlameGraphDataProvider provider, Map idsToNames, String resultFileSuffix, long maxDuration) throws IOException { + String filePrefix = "expectedFgRow" + resultFileSuffix; + // Test getting all the states + Builder builder = ImmutableList.builder(); + for (long i = 0; i < maxDuration; i++) { + builder.add(i); + } + assertRows(provider, idsToNames, builder.build(), filePrefix, "All"); + + // Test getting only the first and last states + assertRows(provider, idsToNames, ImmutableList.of(0L, maxDuration - 1), filePrefix, "2Times"); + + // Test getting the states for the last half of the flamegraph + builder = ImmutableList.builder(); + for (long i = maxDuration / 2; i < maxDuration; i++) { + builder.add(i); + } + assertRows(provider, idsToNames, builder.build(), filePrefix, "Zoom"); + } + + private static Map assertAndGetTree(FlameGraphDataProvider provider, String filePath, @NonNull Map<@NonNull String, @NonNull Object> additionalParameters) throws IOException { + Map<@NonNull String, @NonNull Object> parameters = new HashMap<>(TREE_PARAMETERS); + parameters.putAll(additionalParameters); + @SuppressWarnings("null") + TmfModelResponse> treeResponse = provider.fetchTree(parameters, null); + assertNotNull(treeResponse); + assertEquals(ITmfResponse.Status.COMPLETED, treeResponse.getStatus()); + TmfTreeModel<@NonNull FlameChartEntryModel> treeModel = treeResponse.getModel(); + assertNotNull(treeModel); + List<@NonNull FlameChartEntryModel> treeEntries = treeModel.getEntries(); + + List expectedStrings = Files.readAllLines(Paths.get(EXPECTED_FILE_PATH + filePath)); + assertEquals(expectedStrings.size(), treeEntries.size()); + for (int i = 0; i < expectedStrings.size(); i++) { + String expectedString = expectedStrings.get(i); + String[] split = expectedString.split(","); + + FlameChartEntryModel parent = null; + if (!split[5].equals("-")) { + parent = FlameDataProviderTestUtils.findEntryByNameAndType(treeEntries, split[5], getEntryType(split[4])); + assertNotNull("parent entry of " + split[0] + ' ' + split[1] + WITH_PARENT + split[5], parent); + } + + // Find the entry with type and name with the parent entry, since + // function entries have similar name, look with the parent + FlameChartEntryModel fgEntry = parent == null ? FlameDataProviderTestUtils.findEntryByNameAndType(treeEntries, split[1], getEntryType(split[0])) + : FlameDataProviderTestUtils.findEntryByNameAndType(FlameDataProviderTestUtils.findEntriesByParent(treeEntries, parent.getId()), split[1], getEntryType(split[0])); + assertNotNull("Expecting entry " + split[0] + ' ' + split[1] + WITH_PARENT + split[5], fgEntry); + assertEquals("Start time of entry " + split[0] + ' ' + split[1] + WITH_PARENT + split[5], Long.parseLong(split[2]), fgEntry.getStartTime()); + assertEquals("End time of entry " + split[0] + ' ' + split[1] + WITH_PARENT + split[5], Long.parseLong(split[3]), fgEntry.getEndTime()); + assertEquals("Parent ID of entry " + split[0] + ' ' + split[1] + WITH_PARENT + split[5], parent == null ? -1 : parent.getId(), fgEntry.getParentId()); + } + Map map = new HashMap<>(); + for (FlameChartEntryModel fgModel : treeEntries) { + map.put(fgModel.getId(), fgModel); + } + return map; + } + + @SuppressWarnings("null") + private static FlameChartEntryModel.EntryType getEntryType(String string) { + return FlameChartEntryModel.EntryType.valueOf(string.toUpperCase()); + } + + private static void assertRows(FlameGraphDataProvider provider, Map idsToNames, @NonNull List requestedTimes, String filePath, String descriptor) throws IOException { + @SuppressWarnings("null") + TmfModelResponse rowResponse = provider.fetchRowModel(prepareRowParameters(idsToNames.keySet(), requestedTimes), null); + assertNotNull(rowResponse); + assertEquals(ITmfResponse.Status.COMPLETED, rowResponse.getStatus()); + TimeGraphModel rowModel = rowResponse.getModel(); + assertNotNull(rowModel); + Map rows = new HashMap<>(); + for (ITimeGraphRowModel oneRow : rowModel.getRows()) { + rows.put(oneRow.getEntryID(), oneRow); + } + // ensure row order + Collection models = idsToNames.values(); + + List expectedStrings = Files.readAllLines(Paths.get(EXPECTED_FILE_PATH + filePath + descriptor)); + assertEquals(expectedStrings.size(), rows.size()); + for (int i = 0; i < expectedStrings.size(); i++) { + String expectedString = expectedStrings.get(i); + String[] split = expectedString.split(":"); + FlameChartEntryModel fgEntry = findRowEntry(split[0], models); + assertNotNull(descriptor + ":Entry exists " + split[0], fgEntry); + + ITimeGraphRowModel row = rows.get(fgEntry.getId()); + assertNotNull(descriptor + ": Row entry exists" + split[0], row); + + assertEqualsStates(split[1], row.getStates(), descriptor + ": " + split[0]); + } + } + + private static FlameChartEntryModel findRowEntry(String entryDetails, Collection models) { + String[] details = entryDetails.split(","); + FlameChartEntryModel parentEntry = FlameDataProviderTestUtils.findEntryByNameAndType(models, details[3], getEntryType(details[2])); + if (parentEntry == null) { + return null; + } + return FlameDataProviderTestUtils.findEntryByNameAndType(FlameDataProviderTestUtils.findEntriesByParent(models, parentEntry.getId()), details[1], getEntryType(details[0])); + } + + private static @NonNull Map<@NonNull String, @NonNull Object> prepareRowParameters(@NonNull Set ids, @NonNull List requestedTimes) { + return ImmutableMap.of(DataProviderParameterUtils.REQUESTED_TIME_KEY, requestedTimes, DataProviderParameterUtils.REQUESTED_ITEMS_KEY, ids); + } + + private static void assertEqualsStates(String string, @NonNull List<@NonNull ITimeGraphState> states, String descriptor) { + String[] stringStates = string.split(","); + for (int i = 0; i < stringStates.length / 4; i++) { + assertTrue(descriptor + " has state " + i, states.size() > i); + ITimeGraphState state = states.get(i); + assertEquals(descriptor + ": start time at position " + i, Long.parseLong(stringStates[i * 4]), state.getStartTime()); + assertEquals(descriptor + ": duration at position " + i, Long.parseLong(stringStates[i * 4 + 1]), state.getDuration()); + String strValue = stringStates[i * 4 + 2]; + OutputElementStyle style = state.getStyle(); + if (strValue.equals("-")) { + assertNull(descriptor + ": style at position " + i, style); + } else { + assertNotNull(descriptor + ": existing style at position " + i, style); + String parentKey = style.getParentKey(); + // The style should be a string that represents a number, so + // make sure it can be parsed as integer + try { + Integer.parseInt(parentKey); + String expectedStyle = VALUE_TO_STYLE.computeIfAbsent(strValue, str -> parentKey); + assertEquals(descriptor + ": style at position " + i, expectedStyle, parentKey); + } catch (NumberFormatException e) { + fail("Unexpected style: " + parentKey); + } + } + assertEquals(descriptor + ": no value at position " + i, Integer.MIN_VALUE, state.getValue()); + assertEquals(descriptor + ": label at position " + i, stringStates[i * 4 + 3], String.valueOf(state.getLabel())); + } + assertEquals(descriptor + " no extra state", stringStates.length / 4, states.size()); + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowFull2Times b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowFull2Times new file mode 100644 index 0000000000..4caf7aa6e5 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowFull2Times @@ -0,0 +1,11 @@ +function,0,level,2:0,8,op4,op4 +function,1,level,2:0,8,-,null +function,2,level,2:0,8,-,null +function,0,level,3:0,17,op2,op2 +function,1,level,3:0,1,op3,op3 +function,0,level,6:0,19,op1,op1 +function,1,level,6:0,3,op2,op2,16,3,-,null +function,2,level,6:0,1,op3,op3,5,14,-,null +function,0,level,7:0,19,op5,op5 +function,1,level,7:0,12,op2,op2,12,7,-,null +function,2,level,7:0,1,op3,op3,1,18,-,null diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowFullAll b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowFullAll new file mode 100644 index 0000000000..83783f3f96 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowFullAll @@ -0,0 +1,11 @@ +function,0,level,2:0,8,op4,op4,8,9,op1,op1 +function,1,level,2:0,8,-,null,8,4,op2,op2,12,5,-,null +function,2,level,2:0,8,-,null,8,1,op3,op3,9,8,-,null +function,0,level,3:0,17,op2,op2 +function,1,level,3:0,1,op3,op3,1,6,op2,op2,7,10,-,null +function,0,level,6:0,19,op1,op1 +function,1,level,6:0,3,op2,op2,3,5,op3,op3,8,8,op4,op4,16,3,-,null +function,2,level,6:0,1,op3,op3,1,2,-,null,3,2,op1,op1,5,14,-,null +function,0,level,7:0,19,op5,op5 +function,1,level,7:0,12,op2,op2,12,7,-,null +function,2,level,7:0,1,op3,op3,1,18,-,null diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowFullZoom b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowFullZoom new file mode 100644 index 0000000000..6db4f2848b --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowFullZoom @@ -0,0 +1,11 @@ +function,0,level,2:8,9,op1,op1 +function,1,level,2:8,4,op2,op2,12,5,-,null +function,2,level,2:9,8,-,null +function,0,level,3:0,17,op2,op2 +function,1,level,3:7,10,-,null +function,0,level,6:0,19,op1,op1 +function,1,level,6:8,8,op4,op4,16,3,-,null +function,2,level,6:5,14,-,null +function,0,level,7:0,19,op5,op5 +function,1,level,7:0,12,op2,op2,12,7,-,null +function,2,level,7:1,18,-,null diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowOne2Times b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowOne2Times new file mode 100644 index 0000000000..07c8b23049 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowOne2Times @@ -0,0 +1,3 @@ +function,0,level,All:0,8,op4,op4,44,28,op1,op1 +function,1,level,All:0,8,-,null,64,8,-,null +function,2,level,All:0,25,-,null,51,21,-,null \ No newline at end of file diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowOneAll b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowOneAll new file mode 100644 index 0000000000..9c68cb33a2 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowOneAll @@ -0,0 +1,3 @@ +function,0,level,All:0,8,op4,op4,8,17,op2,op2,25,19,op5,op5,44,28,op1,op1 +function,1,level,All:0,8,-,null,8,1,op3,op3,9,6,op2,op2,15,10,-,null,25,12,op2,op2,37,7,-,null,44,5,op3,op3,49,7,op2,op2,56,8,op4,op4,64,8,-,null +function,2,level,All:0,25,-,null,25,1,op3,op3,26,18,-,null,44,2,op1,op1,46,3,-,null,49,2,op3,op3,51,21,-,null \ No newline at end of file diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowOneZoom b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowOneZoom new file mode 100644 index 0000000000..e0f8c8edb3 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowOneZoom @@ -0,0 +1,3 @@ +function,0,level,All:25,19,op5,op5,44,28,op1,op1 +function,1,level,All:25,12,op2,op2,37,7,-,null,44,5,op3,op3,49,7,op2,op2,56,8,op4,op4,64,8,-,null +function,2,level,All:26,18,-,null,44,2,op1,op1,46,3,-,null,49,2,op3,op3,51,21,-,null \ No newline at end of file diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowProcess2Times b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowProcess2Times new file mode 100644 index 0000000000..18a97cc8c6 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowProcess2Times @@ -0,0 +1,6 @@ +function,0,level,1:0,8,op4,op4 +function,1,level,1:0,8,-,null +function,2,level,1:0,8,-,null +function,0,level,5:0,19,op1,op1,19,19,op5,op5 +function,1,level,5:0,3,op2,op2,31,7,-,null +function,2,level,5:0,1,op3,op3,20,18,-,null \ No newline at end of file diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowProcessAll b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowProcessAll new file mode 100644 index 0000000000..c482f6b7a3 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowProcessAll @@ -0,0 +1,6 @@ +function,0,level,1:0,8,op4,op4,8,9,op1,op1,17,17,op2,op2 +function,1,level,1:0,8,-,null,8,4,op2,op2,12,5,-,null,17,1,op3,op3,18,6,op2,op2,24,10,-,null +function,2,level,1:0,8,-,null,8,1,op3,op3,9,25,-,null +function,0,level,5:0,19,op1,op1,19,19,op5,op5 +function,1,level,5:0,3,op2,op2,3,5,op3,op3,8,8,op4,op4,16,3,-,null,19,12,op2,op2,31,7,-,null +function,2,level,5:0,1,op3,op3,1,2,-,null,3,2,op1,op1,5,14,-,null,19,1,op3,op3,20,18,-,null \ No newline at end of file diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowProcessZoom b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowProcessZoom new file mode 100644 index 0000000000..190327cc76 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowProcessZoom @@ -0,0 +1,6 @@ +function,0,level,1:17,17,op2,op2 +function,1,level,1:18,6,op2,op2,24,10,-,null +function,2,level,1:9,25,-,null +function,0,level,5:19,19,op5,op5 +function,1,level,5:19,12,op2,op2,31,7,-,null +function,2,level,5:19,1,op3,op3,20,18,-,null \ No newline at end of file diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowSelection2Times b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowSelection2Times new file mode 100644 index 0000000000..cc29a44fd8 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowSelection2Times @@ -0,0 +1,10 @@ +function,0,level,2:0,3,op4,op4 +function,1,level,2:0,3,-,null +function,0,level,3:0,10,op2,op2 +function,1,level,3:0,1,op3,op3,7,3,-,null +function,0,level,6:0,10,op1,op1 +function,1,level,6:0,2,op3,op3,8,2,-,null +function,2,level,6:0,1,op1,op1,3,7,-,null +function,0,level,7:0,10,op5,op5 +function,1,level,7:0,5,op2,op2,5,5,-,null +function,2,level,7:0,1,op3,op3,1,9,-,null diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowSelectionAll b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowSelectionAll new file mode 100644 index 0000000000..4fe776247e --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowSelectionAll @@ -0,0 +1,10 @@ +function,0,level,2:0,3,op4,op4,3,5,op1,op1 +function,1,level,2:0,3,-,null,3,2,op2,op2,5,3,-,null +function,0,level,3:0,10,op2,op2 +function,1,level,3:0,1,op3,op3,1,6,op2,op2,7,3,-,null +function,0,level,6:0,10,op1,op1 +function,1,level,6:0,2,op3,op3,2,3,op2,op2,5,3,op4,op4,8,2,-,null +function,2,level,6:0,1,op1,op1,1,1,-,null,2,1,op3,op3,3,7,-,null +function,0,level,7:0,10,op5,op5 +function,1,level,7:0,5,op2,op2,5,5,-,null +function,2,level,7:0,1,op3,op3,1,9,-,null diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowSelectionZoom b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowSelectionZoom new file mode 100644 index 0000000000..4ad99e5795 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgRowSelectionZoom @@ -0,0 +1,10 @@ +function,0,level,2:3,5,op1,op1 +function,1,level,2:5,3,-,null +function,0,level,3:0,10,op2,op2 +function,1,level,3:1,6,op2,op2,7,3,-,null +function,0,level,6:0,10,op1,op1 +function,1,level,6:5,3,op4,op4,8,2,-,null +function,2,level,6:3,7,-,null +function,0,level,7:0,10,op5,op5 +function,1,level,7:5,5,-,null +function,2,level,7:1,9,-,null diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeFull b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeFull new file mode 100644 index 0000000000..c791615791 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeFull @@ -0,0 +1,18 @@ +trace,callstack.xml,0,19,-,- +level,1,0,17,trace,callstack.xml +level,2,0,17,level,1 +function,0,0,17,level,2 +function,1,0,17,level,2 +function,2,0,17,level,2 +level,3,0,17,level,1 +function,0,0,17,level,3 +function,1,0,17,level,3 +level,5,0,19,trace,callstack.xml +level,6,0,19,level,5 +function,0,0,19,level,6 +function,1,0,19,level,6 +function,2,0,19,level,6 +level,7,0,19,level,5 +function,0,0,19,level,7 +function,1,0,19,level,7 +function,2,0,19,level,7 \ No newline at end of file diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeOne b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeOne new file mode 100644 index 0000000000..2c5efdfaa2 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeOne @@ -0,0 +1,5 @@ +trace,callstack.xml,0,72,-,- +level,All,0,72,trace,callstack.xml +function,0,0,72,level,All +function,1,0,72,level,All +function,2,0,72,level,All \ No newline at end of file diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeProcess b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeProcess new file mode 100644 index 0000000000..5d60eb7331 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeProcess @@ -0,0 +1,9 @@ +trace,callstack.xml,0,38,-,- +level,1,0,34,trace,callstack.xml +function,0,0,34,level,1 +function,1,0,34,level,1 +function,2,0,34,level,1 +level,5,0,38,trace,callstack.xml +function,0,0,38,level,5 +function,1,0,38,level,5 +function,2,0,38,level,5 \ No newline at end of file diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeSelection b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeSelection new file mode 100644 index 0000000000..30500c48d5 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/testfiles/dp/expectedFgTreeSelection @@ -0,0 +1,17 @@ +trace,callstack.xml,0,10,-,- +level,1,0,10,trace,callstack.xml +level,2,0,8,level,1 +function,0,0,8,level,2 +function,1,0,8,level,2 +level,3,0,10,level,1 +function,0,0,10,level,3 +function,1,0,10,level,3 +level,5,0,10,trace,callstack.xml +level,6,0,10,level,5 +function,0,0,10,level,6 +function,1,0,10,level,6 +function,2,0,10,level,6 +level,7,0,10,level,5 +function,0,0,10,level,7 +function,1,0,10,level,7 +function,2,0,10,level,7 \ No newline at end of file diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/META-INF/MANIFEST.MF b/analysis/org.eclipse.tracecompass.analysis.callstack.core/META-INF/MANIFEST.MF index 0645af716d..5b208e1789 100644 --- a/analysis/org.eclipse.tracecompass.analysis.callstack.core/META-INF/MANIFEST.MF +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/META-INF/MANIFEST.MF @@ -24,6 +24,7 @@ Require-Bundle: org.eclipse.core.runtime, Export-Package: org.eclipse.tracecompass.internal.analysis.callstack.core;x-friends:="org.eclipse.tracecompass.analysis.callstack.core.tests", org.eclipse.tracecompass.internal.analysis.callstack.core.base;x-friends:="org.eclipse.tracecompass.analysis.callstack.core.tests", org.eclipse.tracecompass.internal.analysis.callstack.core.callgraph;x-friends:="org.eclipse.tracecompass.analysis.callstack.core.tests", + org.eclipse.tracecompass.internal.analysis.callstack.core.flamegraph;x-friends:="org.eclipse.tracecompass.analysis.callstack.core.tests", org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented;x-friends:="org.eclipse.tracecompass.analysis.callstack.core.tests,org.eclipse.tracecompass.analysis.callstack.ui", org.eclipse.tracecompass.internal.analysis.callstack.core.model;x-friends:="org.eclipse.tracecompass.analysis.callstack.core.tests", org.eclipse.tracecompass.internal.analysis.callstack.core.tree;x-friends:="org.eclipse.tracecompass.analysis.callstack.core.tests" diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/plugin.xml b/analysis/org.eclipse.tracecompass.analysis.callstack.core/plugin.xml index 90f94a9ab2..ba7704e6dd 100644 --- a/analysis/org.eclipse.tracecompass.analysis.callstack.core/plugin.xml +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/plugin.xml @@ -14,6 +14,10 @@ + + diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/DataProviderUtils.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/DataProviderUtils.java new file mode 100644 index 0000000000..0133f98c2b --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/DataProviderUtils.java @@ -0,0 +1,61 @@ +/********************************************************************** + * Copyright (c) 2019 École Polytechnique de Montréal + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +package org.eclipse.tracecompass.internal.analysis.callstack.core.flamegraph; + +/** + * Utility class to deal with data providers additional functionalities, like + * actions to execute on certain entries and states. + * + * @author Geneviève Bastien + */ +public final class DataProviderUtils { + + /** + * The prefix character indicating an action tooltip + */ + public static final String ACTION_PREFIX = "#"; //$NON-NLS-1$ + + /** + * The string indicating an action to go to time or range, it should be + * followed by a comma-separated list of long values representing the + * timestamp to go to. + */ + public static final String ACTION_GOTO_TIME = "TIME:"; //$NON-NLS-1$ + + private DataProviderUtils() { + // Private constructor + } + + /** + * Create an action string that means a goto time + * + * @param time + * The time to go to + * @return The action string + */ + public static String createGoToTimeAction(long time) { + return ACTION_GOTO_TIME + time; + } + + /** + * Create an action string that means a goto time range + * + * @param time1 + * The beginning of the time range to go to + * @param time2 + * The end of the time range to go to + * @return The action string + */ + public static String createGoToTimeAction(long time1, long time2) { + return ACTION_GOTO_TIME + time1 + ',' + time2; + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/FlameGraphDataProvider.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/FlameGraphDataProvider.java new file mode 100644 index 0000000000..912c8e8021 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/FlameGraphDataProvider.java @@ -0,0 +1,739 @@ +/******************************************************************************* + * Copyright (c) 2019 École Polytechnique de Montréal + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package org.eclipse.tracecompass.internal.analysis.callstack.core.flamegraph; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.analysis.timing.core.statistics.IStatistics; +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.datastore.core.serialization.ISafeByteBufferWriter; +import org.eclipse.tracecompass.internal.analysis.callstack.core.base.IDataPalette; +import org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented.FlameChartEntryModel; +import org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented.FlameChartEntryModel.EntryType; +import org.eclipse.tracecompass.internal.analysis.callstack.core.model.IHostModel; +import org.eclipse.tracecompass.internal.analysis.callstack.core.tree.AllGroupDescriptor; +import org.eclipse.tracecompass.internal.analysis.callstack.core.tree.ITree; +import org.eclipse.tracecompass.internal.analysis.callstack.core.tree.IWeightedTreeGroupDescriptor; +import org.eclipse.tracecompass.internal.analysis.callstack.core.tree.IWeightedTreeProvider; +import org.eclipse.tracecompass.internal.analysis.callstack.core.tree.IWeightedTreeProvider.MetricType; +import org.eclipse.tracecompass.internal.analysis.callstack.core.tree.IWeightedTreeSet; +import org.eclipse.tracecompass.internal.analysis.callstack.core.tree.WeightedTree; +import org.eclipse.tracecompass.internal.analysis.callstack.core.tree.WeightedTreeGroupBy; +import org.eclipse.tracecompass.internal.provisional.statesystem.core.statevalue.CustomStateValue; +import org.eclipse.tracecompass.segmentstore.core.ISegment; +import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem; +import org.eclipse.tracecompass.statesystem.core.ITmfStateSystemBuilder; +import org.eclipse.tracecompass.statesystem.core.StateSystemFactory; +import org.eclipse.tracecompass.statesystem.core.backend.IStateHistoryBackend; +import org.eclipse.tracecompass.statesystem.core.backend.StateHistoryBackendFactory; +import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException; +import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; +import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue; +import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule; +import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils; +import org.eclipse.tracecompass.tmf.core.model.AbstractTmfTraceDataProvider; +import org.eclipse.tracecompass.tmf.core.model.CommonStatusMessage; +import org.eclipse.tracecompass.tmf.core.model.IOutputStyleProvider; +import org.eclipse.tracecompass.tmf.core.model.OutputStyleModel; +import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphArrow; +import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphDataProvider; +import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphRowModel; +import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphState; +import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphModel; +import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphRowModel; +import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphState; +import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeModel; +import org.eclipse.tracecompass.tmf.core.response.ITmfResponse; +import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse; +import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; +import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; +import org.eclipse.tracecompass.tmf.core.util.Pair; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Multimap; +import com.google.common.collect.TreeMultimap; + +/** + * A data provider for flame graphs, using a {@link IWeightedTreeProvider} as + * input for the data + * + * TODO: Publish the presentation provider + * + * TODO: Find a way to advertise extra parameters (group_by, selection range) + * + * TODO: Use weighted tree instead of callgraph provider + * + * @author Geneviève Bastien + * @param + * The type of objects represented by each node in the tree + * @param + * The type of elements used to group the trees + * @param + * The type of the tree provided + */ +public class FlameGraphDataProvider<@NonNull N, E, @NonNull T extends WeightedTree<@NonNull N>> extends AbstractTmfTraceDataProvider implements ITimeGraphDataProvider, IOutputStyleProvider { + + /** + * Provider ID. + */ + public static final String ID = "org.eclipse.tracecompass.analysis.callstack.core.flamegraph"; //$NON-NLS-1$ + /** + * The key used to specify how to group the entries of the flame graph + */ + public static final String GROUP_BY_KEY = "group_by"; //$NON-NLS-1$ + /** + * The key used to specify a time selection to get the callgraph for. It + * should be a list of 2 longs + */ + public static final String SELECTION_RANGE_KEY = "selection_range"; //$NON-NLS-1$ + /** + * The key used to specify whether to return actions as tooltips, actions + * keys will start with the '#' characters + */ + public static final String TOOLTIP_ACTION_KEY = "actions"; //$NON-NLS-1$ + + private static final AtomicLong ENTRY_ID = new AtomicLong(); + + /** + * Logger for Abstract Tree Data Providers. + */ + private static final Logger LOGGER = TraceCompassLog.getLogger(FlameGraphDataProvider.class); + + /* State System attributes for the root levels */ + private static final String FUNCTION_LEVEL = "::Function"; //$NON-NLS-1$ + + @SuppressWarnings("null") + private final Comparator> fCctComparator2 = Comparator.comparing(WeightedTree::getWeight).thenComparing(s -> String.valueOf(s.getObject())); + + private final IWeightedTreeProvider fWtProvider; + private final String fAnalysisId; + private final long fTraceId = ENTRY_ID.getAndIncrement(); + private final ReentrantReadWriteLock fLock = new ReentrantReadWriteLock(false); + + private final Map fEntries = new HashMap<>(); + private final Map fCgEntries = new HashMap<>(); + private final Map fEndTimes = new HashMap<>(); + + private @Nullable Pair>> fCached; + + private class CacheKey { + private final Map fParameters; + private final IWeightedTreeSet> fTreeSet; + + public CacheKey(Map parameters, IWeightedTreeSet> treeset) { + fParameters = parameters; + fTreeSet = treeset; + } + + @Override + public int hashCode() { + return Objects.hash(fParameters, fTreeSet); + } + + @SuppressWarnings("rawtypes") + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof FlameGraphDataProvider.CacheKey)) { + return false; + } + return Objects.equals(fParameters, ((FlameGraphDataProvider.CacheKey) obj).fParameters) + && Objects.equals(fTreeSet, ((FlameGraphDataProvider.CacheKey) obj).fTreeSet); + } + } + + /** An internal class to describe the data for an entry */ + private static class WeightedTreeEntry { + private ITmfStateSystem fSs; + private Integer fQuark; + + public WeightedTreeEntry(ITmfStateSystem ss, Integer quark) { + fSs = ss; + fQuark = quark; + } + } + + private static class CalleeCustomValue<@NonNull N> extends CustomStateValue { + private WeightedTree fCallSite; + + public CalleeCustomValue(WeightedTree rootFunction) { + fCallSite = rootFunction; + } + + @SuppressWarnings("unchecked") + @Override + public int compareTo(ITmfStateValue o) { + if (!(o instanceof CalleeCustomValue)) { + return -1; + } + return fCallSite.compareTo(((CalleeCustomValue) o).fCallSite); + } + + @Override + protected Byte getCustomTypeId() { + return 103; + } + + @Override + protected void serializeValue(ISafeByteBufferWriter buffer) { + throw new UnsupportedOperationException("This state value is not meant to be written to disk"); //$NON-NLS-1$ + } + + @Override + protected int getSerializedValueSize() { + throw new UnsupportedOperationException("This state value is not meant to be written to disk"); //$NON-NLS-1$ + } + } + + /** + * Constructor + * + * @param trace + * The trace for which this data provider applies + * @param module + * The weighted tree provider encapsulated by this provider + * @param secondaryId + * The ID of the weighted tree provider + */ + public FlameGraphDataProvider(ITmfTrace trace, IWeightedTreeProvider module, String secondaryId) { + super(trace); + fWtProvider = module; + fAnalysisId = secondaryId; + } + + @Override + public String getId() { + return fAnalysisId; + } + + @Override + public @NonNull TmfModelResponse<@NonNull TmfTreeModel<@NonNull FlameChartEntryModel>> fetchTree(@NonNull Map<@NonNull String, @NonNull Object> fetchParameters, @Nullable IProgressMonitor monitor) { + + fLock.writeLock().lock(); + try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameGraphDataProvider#fetchTree") //$NON-NLS-1$ + .setCategory(getClass().getSimpleName()).build()) { + // Did we cache this tree with those parameters and the callgraph? + // For some analyses, the returned callgraph for the same parameters + // may vary if the analysis was done again, we need to cache for + // callgraph as well + SubMonitor subMonitor = Objects.requireNonNull(SubMonitor.convert(monitor, "FlameGraphDataProvider#fetchRowModel", 2)); //$NON-NLS-1$ + IWeightedTreeSet> callGraph = getCallGraph(fetchParameters, subMonitor); + if (callGraph == null) { + return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.TASK_CANCELLED); + } + + CacheKey cacheKey = new CacheKey(fetchParameters, callGraph); + Pair>> cached = fCached; + if (cached != null && cached.getFirst().equals(cacheKey)) { + return cached.getSecond(); + } + + fEntries.clear(); + fCgEntries.clear(); + + if (subMonitor.isCanceled()) { + return new TmfModelResponse<>(null, ITmfResponse.Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED); + } + + long start = 0; + + // Initialize the first element of the tree + List builder = new ArrayList<>(); + @SuppressWarnings("null") + FlameChartEntryModel.Builder traceEntry = new FlameChartEntryModel.Builder(fTraceId, -1, getTrace().getName(), start, FlameChartEntryModel.EntryType.TRACE, -1); + + buildWeightedTreeEntries(callGraph, builder, traceEntry); + + ImmutableList.Builder treeBuilder = ImmutableList.builder(); + long end = traceEntry.getEndTime(); + for (FlameChartEntryModel.Builder builderEntry : builder) { + treeBuilder.add(builderEntry.build()); + end = Math.max(end, builderEntry.getEndTime()); + } + traceEntry.setEndTime(end); + treeBuilder.add(traceEntry.build()); + List tree = treeBuilder.build(); + + tree.forEach(entry -> { + fEntries.put(entry.getId(), entry); + fEndTimes.put(entry.getId(), entry.getEndTime()); + }); + + TmfModelResponse> response = new TmfModelResponse<>(new TmfTreeModel<>(Collections.emptyList(), tree), + ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + fCached = new Pair<>(cacheKey, response); + return response; + } finally { + fLock.writeLock().unlock(); + } + } + + /** + * @param fetchParameters + */ + @SuppressWarnings("unchecked") + private @Nullable IWeightedTreeSet> getCallGraph(Map fetchParameters, SubMonitor subMonitor) { + // Get the provider and wait for the analysis completion + IWeightedTreeProvider wtProvider = fWtProvider; + if (wtProvider instanceof IAnalysisModule) { + ((IAnalysisModule) wtProvider).waitForCompletion(subMonitor); + } + if (subMonitor.isCanceled()) { + return null; + } + + // Get the full or selection callgraph + List selectionRange = DataProviderParameterUtils.extractLongList(fetchParameters, SELECTION_RANGE_KEY); + IWeightedTreeSet<@NonNull N, E, @NonNull T> callGraph; + if (selectionRange == null || selectionRange.size() != 2) { + callGraph = wtProvider.getTreeSet(); + } else { + long time0 = selectionRange.get(0); + long time1 = selectionRange.get(1); + callGraph = wtProvider.getSelection(TmfTimestamp.fromNanos(Math.min(time0, time1)), TmfTimestamp.fromNanos(Math.max(time0, time1))); + } + if (callGraph == null) { + return null; + } + + // Look if we need to group the callgraph + IWeightedTreeGroupDescriptor groupDescriptor = extractGroupDescriptor(fetchParameters, wtProvider); + if (groupDescriptor != null) { + return WeightedTreeGroupBy.groupWeightedTreeBy(groupDescriptor, callGraph, wtProvider); + } + + return (IWeightedTreeSet<@NonNull N, Object, WeightedTree<@NonNull N>>) callGraph; + } + + private static @Nullable IWeightedTreeGroupDescriptor extractGroupDescriptor(Map fetchParameters, IWeightedTreeProvider fcProvider) { + Object groupBy = fetchParameters.get(GROUP_BY_KEY); + if (groupBy == null) { + return null; + } + String groupName = String.valueOf(groupBy); + // Is it the all group descriptor + if (groupName.equals(AllGroupDescriptor.getInstance().getName())) { + return AllGroupDescriptor.getInstance(); + } + // Try to find the right group descriptor + IWeightedTreeGroupDescriptor groupDescriptor = fcProvider.getGroupDescriptor(); + while (groupDescriptor != null) { + if (groupDescriptor.getName().equals(groupName)) { + return groupDescriptor; + } + groupDescriptor = groupDescriptor.getNextGroup(); + } + return null; + } + + private void buildWeightedTreeEntries(IWeightedTreeSet> callGraph, List builder, FlameChartEntryModel.Builder traceEntry) { + IWeightedTreeProvider wtProvider = fWtProvider; + Collection<@NonNull ?> elements = callGraph.getElements(); + for (Object element : elements) { + buildChildrenEntries(element, wtProvider, callGraph, builder, traceEntry); + } + } + + private ITmfStateSystem elementToStateSystem(IWeightedTreeProvider wtProvider, IWeightedTreeSet> callGraph, Object element) { + // Create an in-memory state system for this element + IStateHistoryBackend backend = StateHistoryBackendFactory.createInMemoryBackend("org.eclipse.tracecompass.callgraph.ss", 0L); //$NON-NLS-1$ + ITmfStateSystemBuilder ssb = StateSystemFactory.newStateSystem(backend); + + // Add the functions + List> rootFunctions = new ArrayList<>(callGraph.getTreesFor(element)); + rootFunctions.sort(fCctComparator2); + int quarkFct = ssb.getQuarkAbsoluteAndAdd(FUNCTION_LEVEL); + Deque timestampStack = new ArrayDeque<>(); + timestampStack.push(0L); + for (WeightedTree rootFunction : rootFunctions) { + recursivelyAddChildren(wtProvider, ssb, quarkFct, rootFunction, timestampStack); + } + Long endTime = timestampStack.pop(); + ssb.closeHistory(endTime); + + return ssb; + } + + private void recursivelyAddChildren(IWeightedTreeProvider wtProvider, ITmfStateSystemBuilder ssb, int quarkFct, WeightedTree callSite, Deque timestampStack) { + Long lastEnd = timestampStack.peek(); + if (lastEnd == null) { + return; + } + ssb.pushAttribute(lastEnd, new CalleeCustomValue<>(callSite), quarkFct); + + // Push the children to the state system + timestampStack.push(lastEnd); + List> children = new ArrayList<>(callSite.getChildren()); + children.sort(fCctComparator2); + for (WeightedTree callsite : children) { + recursivelyAddChildren(wtProvider, ssb, quarkFct, callsite, timestampStack); + } + timestampStack.pop(); + + // Add the extra sites + List extraDataSets = wtProvider.getExtraDataSets(); + for (int i = 0; i < extraDataSets.size(); i++) { + Collection> extraDataTrees = callSite.getExtraDataTrees(i); + if (extraDataTrees.isEmpty()) { + continue; + } + String dataSetName = extraDataSets.get(i); + int quarkExtra = ssb.getQuarkAbsoluteAndAdd(dataSetName); + long extraStartTime = lastEnd; + for (WeightedTree<@NonNull N> extraTree : extraDataTrees) { + ssb.modifyAttribute(extraStartTime, new CalleeCustomValue<>(extraTree), quarkExtra); + extraStartTime += extraTree.getWeight(); + } + } + + long currentEnd = timestampStack.pop() + callSite.getWeight(); + timestampStack.push(currentEnd); + ssb.popAttribute(currentEnd, quarkFct); + } + + /** + * Build the entry list for one thread + */ + private void buildChildrenEntries(Object element, IWeightedTreeProvider wtProvider, IWeightedTreeSet> callGraph, List builder, FlameChartEntryModel.Builder parent) { + // Add the entry + FlameChartEntryModel.Builder entry = new FlameChartEntryModel.Builder(ENTRY_ID.getAndIncrement(), + parent.getId(), (element instanceof ITree) ? String.valueOf(((ITree) element).getName()) : String.valueOf(element), 0, FlameChartEntryModel.EntryType.LEVEL, -1); + builder.add(entry); + + // Create the hierarchy of children entries if available + if (element instanceof ITree) { + for (ITree child : ((ITree) element).getChildren()) { + buildChildrenEntries(child, wtProvider, callGraph, builder, entry); + } + } + + // Update endtime with the children and add them to builder + long endTime = entry.getEndTime(); + for (FlameChartEntryModel.Builder childEntry : builder) { + if (childEntry.getParentId() == entry.getId()) { + endTime = Math.max(childEntry.getEndTime(), endTime); + } + } + entry.setEndTime(endTime); + + List> rootTrees = new ArrayList<>(callGraph.getTreesFor(element)); + // Create the function callsite entries + if (rootTrees.isEmpty()) { + return; + } + + Deque timestampStack = new ArrayDeque<>(); + timestampStack.push(0L); + + // Get the state system to represent this callgraph + ITmfStateSystem ss = elementToStateSystem(wtProvider, callGraph, element); + entry.setEndTime(ss.getCurrentEndTime()); + + // Add entry items for the main weighted tree levels + int quark = ss.optQuarkAbsolute(FUNCTION_LEVEL); + if (quark == ITmfStateSystem.INVALID_ATTRIBUTE) { + return; + } + int i = 0; + for (Integer subQuark : ss.getSubAttributes(quark, false)) { + FlameChartEntryModel.Builder child = new FlameChartEntryModel.Builder(ENTRY_ID.getAndIncrement(), entry.getId(), String.valueOf(i), 0, EntryType.FUNCTION, i); + child.setEndTime(ss.getCurrentEndTime()); + builder.add(child); + i++; + fCgEntries.put(child.getId(), new WeightedTreeEntry(ss, subQuark)); + } + + // Add items for the extra entries + List extraDataSets = wtProvider.getExtraDataSets(); + for (int set = 0; set < extraDataSets.size(); set++) { + String dataSetName = extraDataSets.get(set); + quark = ss.optQuarkAbsolute(dataSetName); + if (quark == ITmfStateSystem.INVALID_ATTRIBUTE) { + continue; + } + FlameChartEntryModel.Builder child = new FlameChartEntryModel.Builder(ENTRY_ID.getAndIncrement(), entry.getId(), dataSetName, 0, EntryType.KERNEL, -1); + child.setEndTime(ss.getCurrentEndTime()); + builder.add(child); + fCgEntries.put(child.getId(), new WeightedTreeEntry(ss, quark)); + } + } + + @Override + public @NonNull TmfModelResponse<@NonNull TimeGraphModel> fetchRowModel(@NonNull Map<@NonNull String, @NonNull Object> fetchParameters, @Nullable IProgressMonitor monitor) { + SubMonitor subMonitor = Objects.requireNonNull(SubMonitor.convert(monitor, "FlameGraphDataProvider#fetchRowModel", 2)); //$NON-NLS-1$ + + List times = DataProviderParameterUtils.extractTimeRequested(fetchParameters); + if (times == null) { + return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.INCORRECT_QUERY_PARAMETERS); + } + List rowModels = new ArrayList<>(); + + // Get the selected entries + Collection selected = DataProviderParameterUtils.extractSelectedItems(fetchParameters); + if (selected == null) { + // No entry selected, assume all + selected = fEntries.keySet(); + } + List selectedEntries = new ArrayList<>(); + Multimap> requested = HashMultimap.create(); + + for (Long id : selected) { + WeightedTreeEntry entry = fCgEntries.get(id); + if (entry != null) { + selectedEntries.add(entry); + requested.put(entry, new Pair<>(entry.fQuark, id)); + } + } + + // Prepare the regexes + Map<@NonNull Integer, @NonNull Predicate<@NonNull Multimap<@NonNull String, @NonNull Object>>> predicates = new HashMap<>(); + Multimap<@NonNull Integer, @NonNull String> regexesMap = DataProviderParameterUtils.extractRegexFilter(fetchParameters); + if (regexesMap != null) { + predicates.putAll(computeRegexPredicate(regexesMap)); + } + + if (subMonitor.isCanceled()) { + return new TmfModelResponse<>(null, ITmfResponse.Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED); + } + + // For each element and callgraph, get the states + try { + for (WeightedTreeEntry element : requested.keySet()) { + if (subMonitor.isCanceled()) { + return new TmfModelResponse<>(null, ITmfResponse.Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED); + } + Collection> depths = Objects.requireNonNull(requested.get(element)); + rowModels.addAll(getStatesForElement(times, predicates, subMonitor, element.fSs, depths)); + } + } catch (StateSystemDisposedException e) { + // Nothing to do + } + + return new TmfModelResponse<>(new TimeGraphModel(rowModels), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + + @SuppressWarnings("null") + private List getStatesForElement(List times, Map>> predicates, IProgressMonitor monitor, + ITmfStateSystem ss, Collection> depths) throws StateSystemDisposedException { + List quarks = new ArrayList<>(); + for (Pair pair : depths) { + quarks.add(pair.getFirst()); + } + TreeMultimap intervals = TreeMultimap.create(Comparator.naturalOrder(), + Comparator.comparing(ITmfStateInterval::getStartTime)); + long ssEndTime = ss.getCurrentEndTime(); + for (ITmfStateInterval interval : ss.query2D(quarks, times)) { + if (monitor.isCanceled()) { + return Collections.emptyList(); + } + // Ignore the null intervals of value 1 at the end of the state + // system + if (interval.getStartTime() == ssEndTime && + interval.getStartTime() == interval.getEndTime() && + interval.getValue() == null) { + continue; + } + intervals.put(interval.getAttribute(), interval); + } + + List rows = new ArrayList<>(); + for (Pair pair : depths) { + int quark = pair.getFirst(); + NavigableSet states = intervals.get(quark); + + if (monitor.isCanceled()) { + return Collections.emptyList(); + } + List eventList = new ArrayList<>(); + Long key = Objects.requireNonNull(pair.getSecond()); + states.forEach(i -> { + ITimeGraphState timegraphState = createTimeGraphState(i, ssEndTime); + applyFilterAndAddState(eventList, timegraphState, key, predicates, monitor); + }); + rows.add(new TimeGraphRowModel(key, eventList)); + } + return rows; + } + + @SuppressWarnings("unchecked") + private ITimeGraphState createTimeGraphState(ITmfStateInterval interval, long ssEndTime) { + IWeightedTreeProvider wtProvider = fWtProvider; + long startTime = interval.getStartTime(); + long duration = interval.getEndTime() - startTime + (ssEndTime == interval.getEndTime() ? 0 : 1); + Object valueObject = interval.getValue(); + + if (valueObject instanceof CalleeCustomValue) { + WeightedTree callsite = ((CalleeCustomValue) valueObject).fCallSite; + String displayString = wtProvider.toDisplayString((T) callsite); + return new TimeGraphState(startTime, duration, displayString, fWtProvider.getPalette().getStyleFor(callsite)); + } + return new TimeGraphState(startTime, duration, Integer.MIN_VALUE); + } + + @Override + public @NonNull TmfModelResponse<@NonNull List<@NonNull ITimeGraphArrow>> fetchArrows(@NonNull Map<@NonNull String, @NonNull Object> fetchParameters, @Nullable IProgressMonitor monitor) { + return new TmfModelResponse<>(Collections.emptyList(), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + + @Override + public @NonNull TmfModelResponse<@NonNull Map<@NonNull String, @NonNull String>> fetchTooltip(@NonNull Map<@NonNull String, @NonNull Object> fetchParameters, @Nullable IProgressMonitor monitor) { + List times = DataProviderParameterUtils.extractTimeRequested(fetchParameters); + if (times == null || times.size() != 1) { + return new TmfModelResponse<>(Collections.emptyMap(), ITmfResponse.Status.FAILED, "Invalid time requested for tooltip"); //$NON-NLS-1$ + } + List items = DataProviderParameterUtils.extractSelectedItems(fetchParameters); + if (items == null || items.size() != 1) { + return new TmfModelResponse<>(Collections.emptyMap(), ITmfResponse.Status.FAILED, "Invalid selection requested for tooltip"); //$NON-NLS-1$ + } + Long time = times.get(0); + Long item = items.get(0); + WeightedTreeEntry callGraphEntry = fCgEntries.get(item); + if (callGraphEntry == null) { + return new TmfModelResponse<>(Collections.emptyMap(), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + + WeightedTree<@NonNull N> callSite = findCallSite(callGraphEntry, time); + if (callSite != null) { + Object actions = fetchParameters.get(TOOLTIP_ACTION_KEY); + if (actions == null) { + // Return the normal tooltip + return new TmfModelResponse<>(getTooltip(callSite), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + // Return the actions for this entry + return new TmfModelResponse<>(getTooltipActions(callSite), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + + return new TmfModelResponse<>(Collections.emptyMap(), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + + private Map getTooltipActions(WeightedTree<@NonNull N> callSite) { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + /* Goto min/max actions */ + // Try to get the statistics of the main metric + @SuppressWarnings("unchecked") + IStatistics statistics = fWtProvider.getStatistics((T) callSite, -1); + if (statistics != null) { + Object minObject = statistics.getMinObject(); + if (minObject instanceof ISegment) { + ISegment minimum = (ISegment) minObject; + builder.put(DataProviderUtils.ACTION_PREFIX + Messages.FlameGraph_GoToMin, DataProviderUtils.createGoToTimeAction(minimum.getStart(), minimum.getEnd())); + } + Object maxObject = statistics.getMaxObject(); + if (maxObject instanceof ISegment) { + ISegment maximum = (ISegment) maxObject; + builder.put(DataProviderUtils.ACTION_PREFIX + Messages.FlameGraph_GoToMax, DataProviderUtils.createGoToTimeAction(maximum.getStart(), maximum.getEnd())); + } + } + return builder.build(); + } + + @SuppressWarnings("unchecked") + private Map getTooltip(WeightedTree<@NonNull N> callSite) { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + // Display the object name first + String string = callSite.getObject().toString(); + String displayString = fWtProvider.toDisplayString((T) callSite); + builder.put(Objects.requireNonNull(Messages.FlameGraph_Object), string.equals(displayString) ? displayString : displayString + ' ' + '(' + string + ')'); + List additionalMetrics = fWtProvider.getAdditionalMetrics(); + + // First, display the metrics that do not have statistics to not loose + // them in the middle of stats + MetricType metric = fWtProvider.getWeightType(); + if (!metric.hasStatistics()) { + builder.put(metric.getTitle(), metric.format(callSite.getWeight())); + } + for (int i = 0; i < additionalMetrics.size(); i++) { + MetricType otherMetric = additionalMetrics.get(i); + if (!otherMetric.hasStatistics()) { + builder.put(otherMetric.getTitle(), otherMetric.format(fWtProvider.getAdditionalMetric((T) callSite, i))); + } + } + + // Then, display the metrics with statistics + if (metric.hasStatistics()) { + builder.putAll(getMetricWithStatTooltip(metric, callSite, -1)); + } + for (int i = 0; i < additionalMetrics.size(); i++) { + MetricType otherMetric = additionalMetrics.get(i); + if (otherMetric.hasStatistics()) { + builder.putAll(getMetricWithStatTooltip(otherMetric, callSite, i)); + } + } + + return builder.build(); + } + + @SuppressWarnings("unchecked") + private Map getMetricWithStatTooltip(MetricType metric, WeightedTree<@NonNull N> callSite, int metricIndex) { + Object metricValue = metricIndex < 0 ? callSite.getWeight() : fWtProvider.getAdditionalMetric((T) callSite, metricIndex); + IStatistics statistics = fWtProvider.getStatistics((T) callSite, metricIndex); + Map map = new LinkedHashMap<>(); + if (statistics == null || statistics.getMax() == IHostModel.TIME_UNKNOWN) { + map.put(metric.getTitle(), metric.format(metricValue)); + } else { + map.put(metric.getTitle(), StringUtils.EMPTY); + String lowerTitle = metric.getTitle().toLowerCase(); + map.put("\t" + Messages.FlameGraph_Total + ' ' + lowerTitle, metric.format(statistics.getTotal())); //$NON-NLS-1$ + map.put("\t" + Messages.FlameGraph_Average + ' ' + lowerTitle, metric.format(statistics.getMean())); //$NON-NLS-1$ + map.put("\t" + Messages.FlameGraph_Max + ' ' + lowerTitle, metric.format(statistics.getMax())); //$NON-NLS-1$ + map.put("\t" + Messages.FlameGraph_Min + ' ' + lowerTitle, metric.format(statistics.getMin())); //$NON-NLS-1$ + map.put("\t" + Messages.FlameGraph_Deviation + ' ' + lowerTitle, metric.format(statistics.getStdDev())); //$NON-NLS-1$ + } + return map; + } + + /** Find the callsite at the time and depth requested */ + @SuppressWarnings("unchecked") + private @Nullable WeightedTree<@NonNull N> findCallSite(WeightedTreeEntry cgEntry, Long time) { + try { + ITmfStateInterval interval = cgEntry.fSs.querySingleState(time, cgEntry.fQuark); + + Object valueObject = interval.getValue(); + if (valueObject instanceof CalleeCustomValue) { + return ((CalleeCustomValue) valueObject).fCallSite; + } + } catch (StateSystemDisposedException e) { + // Nothing to do + } + return null; + } + + @Override + public TmfModelResponse fetchStyle(Map fetchParameters, @Nullable IProgressMonitor monitor) { + IDataPalette palette = fWtProvider.getPalette(); + return new TmfModelResponse<>(new OutputStyleModel(palette.getStyles()), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/FlameGraphDataProviderFactory.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/FlameGraphDataProviderFactory.java new file mode 100644 index 0000000000..0ca627c776 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/FlameGraphDataProviderFactory.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * 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 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package org.eclipse.tracecompass.internal.analysis.callstack.core.flamegraph; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.internal.analysis.callstack.core.tree.IWeightedTreeProvider; +import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule; +import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderFactory; +import org.eclipse.tracecompass.tmf.core.model.timegraph.TmfTimeGraphCompositeDataProvider; +import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataModel; +import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataProvider; +import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; +import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; + +import com.google.common.annotations.VisibleForTesting; + +/** + * Factory for the flame graph data provider + * + * @author Geneviève Bastien + */ +public class FlameGraphDataProviderFactory implements IDataProviderFactory { + + private static Map> INSTANCES = new HashMap<>(); + + @Override + public @Nullable ITmfTreeDataProvider createProvider(ITmfTrace trace) { + // Need the analysis + return null; + } + + @Override + public @Nullable ITmfTreeDataProvider createProvider(ITmfTrace trace, String secondaryId) { + Collection traces = TmfTraceManager.getTraceSetWithExperiment(trace); + // Create a composite data provider for all traces + List> providers = new ArrayList<>(); + for (ITmfTrace child : traces) { + FlameGraphDataProvider childProvider = create(child, secondaryId); + if (childProvider != null) { + providers.add(childProvider); + } + } + if (providers.isEmpty()) { + return null; + } else if (providers.size() == 1) { + return providers.get(0); + } + return new TmfTimeGraphCompositeDataProvider<>(providers, FlameGraphDataProvider.ID + ':' + secondaryId); + } + + private static @Nullable FlameGraphDataProvider create(ITmfTrace trace, String secondaryId) { + FlameGraphDataProvider dataProvider = INSTANCES.get(secondaryId); + if (dataProvider != null) { + return dataProvider; + } + // The trace can be an experiment, so we need to know if there are + // multiple analysis modules with the same ID + IAnalysisModule analysisModule = trace.getAnalysisModule(secondaryId); + if (!(analysisModule instanceof IWeightedTreeProvider)) { + return null; + } + analysisModule.schedule(); + return new FlameGraphDataProvider<>(trace, (IWeightedTreeProvider) analysisModule, FlameGraphDataProvider.ID + ':' + secondaryId); + } + + /** + * Adds a reference to a data provider identified by the id, but not + * associated with a trace. Useful for data provider unit testing where + * fixtures of data are used without a trace + * + * @param id + * ID of the data provider. A null value will remove + * the data provider from the instance list + * @param dataProvider + * The data provider + */ + @VisibleForTesting + public static void registerDataProviderWithId(String id, @Nullable FlameGraphDataProvider dataProvider) { + if (dataProvider == null) { + INSTANCES.remove(id); + return; + } + INSTANCES.put(id, dataProvider); + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/Messages.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/Messages.java new file mode 100644 index 0000000000..24d431baa6 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/Messages.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2016 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package org.eclipse.tracecompass.internal.analysis.callstack.core.flamegraph; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.osgi.util.NLS; + +/** + * Message bundle for the flame graph view + * + * @author Sonia Farrah + */ +@NonNullByDefault({}) +public class Messages extends NLS { + private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$ + + /** Label for the object */ + public static String FlameGraph_Object; + /** Label for the total time */ + public static String FlameGraph_Total; + /** Label for average time */ + public static String FlameGraph_Average; + /** Label for the minimum value */ + public static String FlameGraph_Min; + /** Label for the maximum value */ + public static String FlameGraph_Max; + /** Label for standard deviation */ + public static String FlameGraph_Deviation; + /** Label for the goto min action */ + public static String FlameGraph_GoToMin; + /** Label for the goto max action */ + public static String FlameGraph_GoToMax; + + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/messages.properties b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/messages.properties new file mode 100644 index 0000000000..4aec31fdfa --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/messages.properties @@ -0,0 +1,19 @@ +############################################################################### +# Copyright (c) 2019 École Polytechnique de Montréal +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License 2.0 +# which accompanies this distribution, and is available at +# https://www.eclipse.org/legal/epl-2.0 +# +# SPDX-License-Identifier: EPL-2.0 +############################################################################### + +FlameGraph_Object=Object +FlameGraph_Total=Total +FlameGraph_Average=Mean +FlameGraph_Min=Minimum +FlameGraph_Max=Maximum +FlameGraph_Deviation=Deviation +FlameGraph_GoToMin=Go to minimum +FlameGraph_GoToMax=Go to maximum diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/package-info.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/package-info.java new file mode 100644 index 0000000000..27080da180 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/flamegraph/package-info.java @@ -0,0 +1,12 @@ +/******************************************************************************* + * Copyright (c) 2023 Ericsson + * + * All rights reserved. This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 which accompanies + * this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +@org.eclipse.jdt.annotation.NonNullByDefault +package org.eclipse.tracecompass.internal.analysis.callstack.core.flamegraph;