| /******************************************************************************* |
| * 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 v1.0 which |
| * accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| *******************************************************************************/ |
| |
| package org.eclipse.tracecompass.incubator.callstack.core.tests.callgraph; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| |
| 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.Objects; |
| import java.util.Set; |
| |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.tracecompass.incubator.callstack.core.callgraph.AllGroupDescriptor; |
| import org.eclipse.tracecompass.incubator.callstack.core.tests.flamechart.CallStackTestBase; |
| import org.eclipse.tracecompass.incubator.callstack.core.tests.stubs.CallStackAnalysisStub; |
| import org.eclipse.tracecompass.incubator.callstack.core.tests.stubs.FlameDataProviderTestUtils; |
| import org.eclipse.tracecompass.incubator.internal.callstack.core.flamegraph.FlameGraphDataProvider; |
| import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartEntryModel; |
| import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils; |
| 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 @NonNull Map<@NonNull String, @NonNull Object> TREE_PARAMETERS = ImmutableMap.of( |
| DataProviderParameterUtils.REQUESTED_TIME_KEY, ImmutableList.of(0, Long.MAX_VALUE)); |
| |
| /** |
| * 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); |
| 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; |
| } |
| |
| 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 { |
| 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]; |
| assertEquals(descriptor + ": value at position " + i, strValue.equals("-") ? Integer.MIN_VALUE : Objects.hash(strValue), 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()); |
| } |
| |
| } |