Skip to content

Commit

Permalink
callstack: Add FlameGraphDataProvider|Factory|Test
Browse files Browse the repository at this point in the history
-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 <marco.miller@ericsson.com>
Reviewed-on: https://git.eclipse.org/r/c/tracecompass/org.eclipse.tracecompass/+/199828
Tested-by: Trace Compass Bot <tracecompass-bot@eclipse.org>
Tested-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
Reviewed-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
  • Loading branch information
marco-miller committed Mar 31, 2023
1 parent b5167f1 commit 2936de9
Show file tree
Hide file tree
Showing 25 changed files with 1,392 additions and 0 deletions.
@@ -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<String, String> 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<Long, FlameChartEntryModel> 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<Long, FlameChartEntryModel> 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<Long, FlameChartEntryModel> 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<Long, FlameChartEntryModel> 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<Long, FlameChartEntryModel> idsToNames, String resultFileSuffix, long maxDuration) throws IOException {
String filePrefix = "expectedFgRow" + resultFileSuffix;
// Test getting all the states
Builder<Long> 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<Long, FlameChartEntryModel> 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<TmfTreeModel<@NonNull FlameChartEntryModel>> 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<String> 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<Long, FlameChartEntryModel> 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<Long, FlameChartEntryModel> idsToNames, @NonNull List<Long> requestedTimes, String filePath, String descriptor) throws IOException {
@SuppressWarnings("null")
TmfModelResponse<TimeGraphModel> rowResponse = provider.fetchRowModel(prepareRowParameters(idsToNames.keySet(), requestedTimes), null);
assertNotNull(rowResponse);
assertEquals(ITmfResponse.Status.COMPLETED, rowResponse.getStatus());
TimeGraphModel rowModel = rowResponse.getModel();
assertNotNull(rowModel);
Map<Long, @NonNull ITimeGraphRowModel> rows = new HashMap<>();
for (ITimeGraphRowModel oneRow : rowModel.getRows()) {
rows.put(oneRow.getEntryID(), oneRow);
}
// ensure row order
Collection<FlameChartEntryModel> models = idsToNames.values();

List<String> 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<FlameChartEntryModel> 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<Long> ids, @NonNull List<Long> 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());
}
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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

0 comments on commit 2936de9

Please sign in to comment.