callstack: Add a flamegraph data provider

Change-Id: Iee70a5d01750162cb7953b41b652b33a714b20e5
Signed-off-by: Geneviève Bastien <gbastien+lttng@versatic.net>
Reviewed-on: https://git.eclipse.org/r/149297
Tested-by: CI Bot
Reviewed-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/callgraph/FlameGraphDataProviderTest.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/callgraph/FlameGraphDataProviderTest.java
new file mode 100644
index 0000000..9ad0b01
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/callgraph/FlameGraphDataProviderTest.java
@@ -0,0 +1,256 @@
+/*******************************************************************************
+ * 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), 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()));
+        }
+        assertTrue(descriptor + " no extra state", states.size() == stringStates.length / 4);
+    }
+
+}
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/flamechart/FlameChartDataProviderTest.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/flamechart/FlameChartDataProviderTest.java
index bab716e..af1efde 100644
--- a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/flamechart/FlameChartDataProviderTest.java
+++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/src/org/eclipse/tracecompass/incubator/callstack/core/tests/flamechart/FlameChartDataProviderTest.java
@@ -21,13 +21,13 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.tracecompass.incubator.callstack.core.tests.stubs.CallStackAnalysisStub;
+import org.eclipse.tracecompass.incubator.callstack.core.tests.stubs.FlameDataProviderTestUtils;
 import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartDataProvider;
 import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartDataProviderFactory;
 import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartEntryModel;
@@ -46,13 +46,13 @@
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
 
 /**
  * Test the {@link FlameChartDataProvider} class
  *
  * @author Geneviève Bastien
  */
+@SuppressWarnings("restriction")
 public class FlameChartDataProviderTest extends CallStackTestBase {
 
     private static final @Nullable IProgressMonitor MONITOR = new NullProgressMonitor();
@@ -117,7 +117,7 @@
 
         // Test the hierarchy of the tree
         for (FlameChartEntryModel entry : modelEntries) {
-            FlameChartEntryModel parent = findEntryById(modelEntries, entry.getParentId());
+            FlameChartEntryModel parent = FlameDataProviderTestUtils.findEntryById(modelEntries, entry.getParentId());
             switch (entry.getEntryType()) {
             case FUNCTION:
                 assertNotNull(parent);
@@ -164,30 +164,6 @@
         }
     }
 
-    private static @Nullable FlameChartEntryModel findEntryById(Collection<FlameChartEntryModel> list, long id) {
-        return list.stream()
-                .filter(entry -> entry.getId() == id)
-                .findFirst().orElse(null);
-    }
-
-    private static @Nullable FlameChartEntryModel findEntryByNameAndType(Collection<FlameChartEntryModel> list, String name, EntryType type) {
-        return list.stream()
-                .filter(entry -> entry.getEntryType().equals(type) && entry.getName().equals(name))
-                .findFirst().orElse(null);
-    }
-
-    private static @Nullable FlameChartEntryModel findEntryByDepthAndType(Collection<FlameChartEntryModel> list, int depth, EntryType type) {
-        return list.stream()
-                .filter(entry -> entry.getEntryType().equals(type) && entry.getDepth() == depth)
-                .findFirst().orElse(null);
-    }
-
-    private static List<FlameChartEntryModel> findEntriesByParent(Collection<FlameChartEntryModel> list, long parentId) {
-        return list.stream()
-                .filter(entry -> entry.getParentId() == parentId)
-                .collect(Collectors.toList());
-    }
-
     /**
      * Test getting the model from the flame chart data provider
      */
@@ -203,21 +179,21 @@
         // Find the entries corresponding to threads 3 and 6 (along with pid 5)
         Set<@NonNull Long> selectedIds = new HashSet<>();
         // Thread 3
-        FlameChartEntryModel tid3 = findEntryByNameAndType(modelEntries, "3", EntryType.LEVEL);
+        FlameChartEntryModel tid3 = FlameDataProviderTestUtils.findEntryByNameAndType(modelEntries, "3", EntryType.LEVEL);
         assertNotNull(tid3);
         selectedIds.add(tid3.getId());
-        List<FlameChartEntryModel> tid3Children = findEntriesByParent(modelEntries, tid3.getId());
+        List<FlameChartEntryModel> tid3Children = FlameDataProviderTestUtils.findEntriesByParent(modelEntries, tid3.getId());
         assertEquals(2, tid3Children.size());
         tid3Children.forEach(child -> selectedIds.add(child.getId()));
         // Pid 5
-        FlameChartEntryModel pid5 = findEntryByNameAndType(modelEntries, "5", EntryType.LEVEL);
+        FlameChartEntryModel pid5 = FlameDataProviderTestUtils.findEntryByNameAndType(modelEntries, "5", EntryType.LEVEL);
         assertNotNull(pid5);
         selectedIds.add(pid5.getId());
         // Thread 6
-        FlameChartEntryModel tid6 = findEntryByNameAndType(modelEntries, "6", EntryType.LEVEL);
+        FlameChartEntryModel tid6 = FlameDataProviderTestUtils.findEntryByNameAndType(modelEntries, "6", EntryType.LEVEL);
         assertNotNull(tid6);
         selectedIds.add(tid6.getId());
-        List<FlameChartEntryModel> tid6Children = findEntriesByParent(modelEntries, tid6.getId());
+        List<FlameChartEntryModel> tid6Children = FlameDataProviderTestUtils.findEntriesByParent(modelEntries, tid6.getId());
         assertEquals(3, tid6Children.size());
         tid6Children.forEach(child -> selectedIds.add(child.getId()));
 
@@ -235,25 +211,25 @@
         verifyStates(rows, pid5, Collections.emptyList());
         verifyStates(rows, tid6, Collections.emptyList());
         // Verify function level 1 of tid 3
-        verifyStates(rows, findEntryByDepthAndType(tid3Children, 1, EntryType.FUNCTION), ImmutableList.of(new TimeGraphState(3, 17, Integer.MIN_VALUE, "op2")));
+        verifyStates(rows, FlameDataProviderTestUtils.findEntryByDepthAndType(tid3Children, 1, EntryType.FUNCTION), ImmutableList.of(new TimeGraphState(3, 17, Integer.MIN_VALUE, "op2")));
         // Verify function level 2 of tid 3
-        verifyStates(rows, findEntryByDepthAndType(tid3Children, 2, EntryType.FUNCTION), ImmutableList.of(
+        verifyStates(rows, FlameDataProviderTestUtils.findEntryByDepthAndType(tid3Children, 2, EntryType.FUNCTION), ImmutableList.of(
                 new TimeGraphState(1, 4, Integer.MIN_VALUE),
                 new TimeGraphState(5, 1, Integer.MIN_VALUE, "op3"),
                 new TimeGraphState(6, 1, Integer.MIN_VALUE),
                 new TimeGraphState(7, 6, Integer.MIN_VALUE, "op2"),
                 new TimeGraphState(13, 8, Integer.MIN_VALUE)));
         // Verify function level 1 of tid 6
-        verifyStates(rows, findEntryByDepthAndType(tid6Children, 1, EntryType.FUNCTION), ImmutableList.of(new TimeGraphState(1, 19, Integer.MIN_VALUE, "op1")));
+        verifyStates(rows, FlameDataProviderTestUtils.findEntryByDepthAndType(tid6Children, 1, EntryType.FUNCTION), ImmutableList.of(new TimeGraphState(1, 19, Integer.MIN_VALUE, "op1")));
         // Verify function level 2 of tid 6
-        verifyStates(rows, findEntryByDepthAndType(tid6Children, 2, EntryType.FUNCTION), ImmutableList.of(
+        verifyStates(rows, FlameDataProviderTestUtils.findEntryByDepthAndType(tid6Children, 2, EntryType.FUNCTION), ImmutableList.of(
                 new TimeGraphState(2, 5, Integer.MIN_VALUE, "op3"),
                 new TimeGraphState(7, 1, Integer.MIN_VALUE),
                 new TimeGraphState(8, 3, Integer.MIN_VALUE, "op2"),
                 new TimeGraphState(11, 1, Integer.MIN_VALUE),
                 new TimeGraphState(12, 8, Integer.MIN_VALUE, "op4")));
         // Verify function level 3 of tid 6
-        verifyStates(rows, findEntryByDepthAndType(tid6Children, 3, EntryType.FUNCTION), ImmutableList.of(
+        verifyStates(rows, FlameDataProviderTestUtils.findEntryByDepthAndType(tid6Children, 3, EntryType.FUNCTION), ImmutableList.of(
                 new TimeGraphState(1, 3, Integer.MIN_VALUE),
                 new TimeGraphState(4, 2, Integer.MIN_VALUE, "op1"),
                 new TimeGraphState(6, 3, Integer.MIN_VALUE),
@@ -274,19 +250,19 @@
         verifyStates(rows, pid5, Collections.emptyList());
         verifyStates(rows, tid6, Collections.emptyList());
         // Verify function level 1 of tid 3
-        verifyStates(rows, findEntryByDepthAndType(tid3Children, 1, EntryType.FUNCTION), ImmutableList.of(new TimeGraphState(3, 17, Integer.MIN_VALUE, "op2")));
+        verifyStates(rows, FlameDataProviderTestUtils.findEntryByDepthAndType(tid3Children, 1, EntryType.FUNCTION), ImmutableList.of(new TimeGraphState(3, 17, Integer.MIN_VALUE, "op2")));
         // Verify function level 2 of tid 3
-        verifyStates(rows, findEntryByDepthAndType(tid3Children, 2, EntryType.FUNCTION), ImmutableList.of(
+        verifyStates(rows, FlameDataProviderTestUtils.findEntryByDepthAndType(tid3Children, 2, EntryType.FUNCTION), ImmutableList.of(
                 new TimeGraphState(1, 4, Integer.MIN_VALUE),
                 new TimeGraphState(13, 8, Integer.MIN_VALUE)));
         // Verify function level 1 of tid 6
-        verifyStates(rows, findEntryByDepthAndType(tid6Children, 1, EntryType.FUNCTION), ImmutableList.of(new TimeGraphState(1, 19, Integer.MIN_VALUE, "op1")));
+        verifyStates(rows, FlameDataProviderTestUtils.findEntryByDepthAndType(tid6Children, 1, EntryType.FUNCTION), ImmutableList.of(new TimeGraphState(1, 19, Integer.MIN_VALUE, "op1")));
         // Verify function level 2 of tid 6
-        verifyStates(rows, findEntryByDepthAndType(tid6Children, 2, EntryType.FUNCTION), ImmutableList.of(
+        verifyStates(rows, FlameDataProviderTestUtils.findEntryByDepthAndType(tid6Children, 2, EntryType.FUNCTION), ImmutableList.of(
                 new TimeGraphState(2, 5, Integer.MIN_VALUE, "op3"),
                 new TimeGraphState(12, 8, Integer.MIN_VALUE, "op4")));
         // Verify function level 3 of tid 6
-        verifyStates(rows, findEntryByDepthAndType(tid6Children, 3, EntryType.FUNCTION), ImmutableList.of(
+        verifyStates(rows, FlameDataProviderTestUtils.findEntryByDepthAndType(tid6Children, 3, EntryType.FUNCTION), ImmutableList.of(
                 new TimeGraphState(1, 3, Integer.MIN_VALUE),
                 new TimeGraphState(10, 11, Integer.MIN_VALUE)));
     }
@@ -305,9 +281,9 @@
         List<@NonNull FlameChartEntryModel> modelEntries = model.getEntries();
 
         // Thread 2
-        FlameChartEntryModel tid2 = findEntryByNameAndType(modelEntries, "2", EntryType.LEVEL);
+        FlameChartEntryModel tid2 = FlameDataProviderTestUtils.findEntryByNameAndType(modelEntries, "2", EntryType.LEVEL);
         assertNotNull(tid2);
-        List<FlameChartEntryModel> tid2Children = findEntriesByParent(modelEntries, tid2.getId());
+        List<FlameChartEntryModel> tid2Children = FlameDataProviderTestUtils.findEntriesByParent(modelEntries, tid2.getId());
         assertEquals(3, tid2Children.size());
 
         // For each child, make sure the response is always the same
@@ -331,23 +307,23 @@
         verifyFollowResponse(rowModel, -1, -1);
 
         // Go backward from the back
-        rowModel = dataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(Lists.newArrayList(Long.MIN_VALUE, 20L), selectedEntry)), new NullProgressMonitor());
+        rowModel = dataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(ImmutableList.of(Long.MIN_VALUE, 20L), selectedEntry)), new NullProgressMonitor());
         verifyFollowResponse(rowModel, 1, 12);
 
         // Go backward from time 7 till the beginning
-        rowModel = dataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(Lists.newArrayList(Long.MIN_VALUE, 7L), selectedEntry)), new NullProgressMonitor());
+        rowModel = dataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(ImmutableList.of(Long.MIN_VALUE, 7L), selectedEntry)), new NullProgressMonitor());
         verifyFollowResponse(rowModel, 2, 5);
 
-        rowModel = dataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(Lists.newArrayList(Long.MIN_VALUE, 5L), selectedEntry)), new NullProgressMonitor());
+        rowModel = dataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(ImmutableList.of(Long.MIN_VALUE, 5L), selectedEntry)), new NullProgressMonitor());
         verifyFollowResponse(rowModel, 3, 4);
 
-        rowModel = dataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(Lists.newArrayList(Long.MIN_VALUE, 4L), selectedEntry)), new NullProgressMonitor());
+        rowModel = dataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(ImmutableList.of(Long.MIN_VALUE, 4L), selectedEntry)), new NullProgressMonitor());
         verifyFollowResponse(rowModel, 2, 3);
 
-        rowModel = dataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(Lists.newArrayList(Long.MIN_VALUE, 3L), selectedEntry)), new NullProgressMonitor());
+        rowModel = dataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(ImmutableList.of(Long.MIN_VALUE, 3L), selectedEntry)), new NullProgressMonitor());
         verifyFollowResponse(rowModel, 1, 1);
 
-        rowModel = dataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(Lists.newArrayList(Long.MIN_VALUE, 1L), selectedEntry)), new NullProgressMonitor());
+        rowModel = dataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(ImmutableList.of(Long.MIN_VALUE, 1L), selectedEntry)), new NullProgressMonitor());
         verifyFollowResponse(rowModel, -1, -1);
     }
 
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/stubs/org/eclipse/tracecompass/incubator/callstack/core/tests/stubs/FlameDataProviderTestUtils.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/stubs/org/eclipse/tracecompass/incubator/callstack/core/tests/stubs/FlameDataProviderTestUtils.java
new file mode 100644
index 0000000..720af63
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/stubs/org/eclipse/tracecompass/incubator/callstack/core/tests/stubs/FlameDataProviderTestUtils.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * 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.stubs;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartEntryModel;
+import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartEntryModel.EntryType;
+
+/**
+ * Utility methods to manipulate flame chart/graph data provider data
+ *
+ * @author Geneviève Bastien
+ */
+public class FlameDataProviderTestUtils {
+
+    /**
+     * Find an entry by its ID
+     *
+     * @param list
+     *            The list of flame chart entries
+     * @param id
+     *            The ID to search for
+     * @return The entry, or <code>null</code> if the entry is unavailable
+     */
+    public static @Nullable FlameChartEntryModel findEntryById(Collection<FlameChartEntryModel> list, long id) {
+        return list.stream()
+                .filter(entry -> entry.getId() == id)
+                .findFirst().orElse(null);
+    }
+
+    /**
+     * Find an entry by its name and type
+     *
+     * @param list
+     *            The list of flame chart entries
+     * @param name
+     *            The name of the entry
+     * @param type
+     *            The type of the entry
+     * @return The entry, or <code>null</code> if the entry is unavailable
+     */
+    public static @Nullable FlameChartEntryModel findEntryByNameAndType(Collection<FlameChartEntryModel> list, String name, EntryType type) {
+        return list.stream()
+                .filter(entry -> entry.getEntryType().equals(type) && entry.getName().equals(name))
+                .findFirst().orElse(null);
+    }
+
+    /**
+     * Find an entry by its depth and type
+     *
+     * @param list The list of flame chart entries
+     * @param depth
+     * @param type
+     * @return The entry, or <code>null</code> if the entry is unavailable
+     */
+    public static @Nullable FlameChartEntryModel findEntryByDepthAndType(Collection<FlameChartEntryModel> list, int depth, EntryType type) {
+        return list.stream()
+                .filter(entry -> entry.getEntryType().equals(type) && entry.getDepth() == depth)
+                .findFirst().orElse(null);
+    }
+
+    /**
+     * @param list
+     * @param parentId
+     * @return
+     */
+    public static List<FlameChartEntryModel> findEntriesByParent(Collection<FlameChartEntryModel> list, long parentId) {
+        return list.stream()
+                .filter(entry -> entry.getParentId() == parentId)
+                .collect(Collectors.toList());
+    }
+
+}
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowFull2Times b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowFull2Times
new file mode 100644
index 0000000..1667cd4
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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,16,3,-,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,12,7,-,null
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowFullAll b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowFullAll
new file mode 100644
index 0000000..bbfa892
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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,3,-,null,12,5,-,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,3,-,null,8,8,-,null,16,3,-,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,11,-,null,12,7,-,null
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowFullZoom b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowFullZoom
new file mode 100644
index 0000000..17bc171
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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:8,1,op3,op3,9,3,-,null,12,5,-,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:8,8,-,null,16,3,-,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,11,-,null,12,7,-,null
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowOne2Times b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowOne2Times
new file mode 100644
index 0000000..f9b175c
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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,8,-,null,64,8,-,null
\ No newline at end of file
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowOneAll b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowOneAll
new file mode 100644
index 0000000..6e97b45
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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,8,-,null,8,17,-,null,25,1,op3,op3,26,11,-,null,37,7,-,null,44,2,op1,op1,46,3,-,null,49,2,op3,op3,51,5,-,null,56,8,-,null,64,8,-,null
\ No newline at end of file
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowOneZoom b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowOneZoom
new file mode 100644
index 0000000..4ac2502
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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,11,-,null,37,7,-,null,44,2,op1,op1,46,3,-,null,49,2,op3,op3,51,5,-,null,56,8,-,null,64,8,-,null
\ No newline at end of file
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowProcess2Times b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowProcess2Times
new file mode 100644
index 0000000..6267e99
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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,31,7,-,null
\ No newline at end of file
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowProcessAll b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowProcessAll
new file mode 100644
index 0000000..51a260c
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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,3,-,null,12,5,-,null,17,17,-,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,3,-,null,8,8,-,null,16,3,-,null,19,1,op3,op3,20,11,-,null,31,7,-,null
\ No newline at end of file
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowProcessZoom b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowProcessZoom
new file mode 100644
index 0000000..bb2aa59
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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:17,17,-,null
+function,0,level,5:0,19,op1,op1,19,19,op5,op5
+function,1,level,5:16,3,-,null,19,12,op2,op2,31,7,-,null
+function,2,level,5:16,3,-,null,19,1,op3,op3,20,11,-,null,31,7,-,null
\ No newline at end of file
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowSelection2Times b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowSelection2Times
new file mode 100644
index 0000000..6a7fe5f
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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,8,2,-,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,5,5,-,null
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowSelectionAll b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowSelectionAll
new file mode 100644
index 0000000..6b3e73f
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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,2,-,null,5,3,-,null,8,2,-,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,4,-,null,5,5,-,null
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowSelectionZoom b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowSelectionZoom
new file mode 100644
index 0000000..63b426a
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgRowSelectionZoom
@@ -0,0 +1,10 @@
+function,0,level,2:3,5,op1,op1
+function,1,level,2:3,2,op2,op2,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:2,3,op2,op2,5,3,op4,op4,8,2,-,null
+function,2,level,6:3,2,-,null,5,3,-,null,8,2,-,null
+function,0,level,7:0,10,op5,op5
+function,1,level,7:0,5,op2,op2,5,5,-,null
+function,2,level,7:1,4,-,null,5,5,-,null
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgTreeFull b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgTreeFull
new file mode 100644
index 0000000..c791615
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgTreeOne b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgTreeOne
new file mode 100644
index 0000000..2c5efdf
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgTreeProcess b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgTreeProcess
new file mode 100644
index 0000000..d71b3be
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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,17,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/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgTreeSelection b/callstack/org.eclipse.tracecompass.incubator.callstack.core.tests/testfiles/dp/expectedFgTreeSelection
new file mode 100644
index 0000000..30500c4
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.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/callstack/org.eclipse.tracecompass.incubator.callstack.core/META-INF/MANIFEST.MF b/callstack/org.eclipse.tracecompass.incubator.callstack.core/META-INF/MANIFEST.MF
index 106d1f7..2cc39a4 100644
--- a/callstack/org.eclipse.tracecompass.incubator.callstack.core/META-INF/MANIFEST.MF
+++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/META-INF/MANIFEST.MF
@@ -34,6 +34,7 @@
  org.eclipse.tracecompass.incubator.callstack.core.symbol,
  org.eclipse.tracecompass.incubator.internal.callstack.core;x-friends:="org.eclipse.tracecompass.incubator.callstack.core.tests,org.eclipse.tracecompass.incubator.callstack.ui",
  org.eclipse.tracecompass.incubator.internal.callstack.core.base;x-friends:="org.eclipse.tracecompass.incubator.callstack.core.tests,org.eclipse.tracecompass.incubator.callstack.ui",
+ org.eclipse.tracecompass.incubator.internal.callstack.core.flamegraph;x-friends:="org.eclipse.tracecompass.incubator.callstack.core.tests,org.eclipse.tracecompass.incubator.callstack.ui",
  org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented;x-friends:="org.eclipse.tracecompass.incubator.callstack.core.tests,org.eclipse.tracecompass.incubator.callstack.ui",
  org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.callgraph;x-friends:="org.eclipse.tracecompass.incubator.callstack.core.tests,org.eclipse.tracecompass.incubator.callstack.ui",
  org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider,
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/plugin.xml b/callstack/org.eclipse.tracecompass.incubator.callstack.core/plugin.xml
index 253fe11..12e7212 100644
--- a/callstack/org.eclipse.tracecompass.incubator.callstack.core/plugin.xml
+++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/plugin.xml
@@ -24,6 +24,10 @@
    <extension
          point="org.eclipse.tracecompass.tmf.core.dataprovider">
       <dataProviderFactory
+            class="org.eclipse.tracecompass.incubator.internal.callstack.core.flamegraph.FlameGraphDataProviderFactory"
+            id="org.eclipse.tracecompass.incubator.internal.callstack.core.flamegraph.provider">
+      </dataProviderFactory>
+      <dataProviderFactory
             class="org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartDataProviderFactory"
             id="org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.flamechart">
       </dataProviderFactory>
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/FlameGraphDataProvider.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/FlameGraphDataProvider.java
new file mode 100644
index 0000000..f6f4315
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/FlameGraphDataProvider.java
@@ -0,0 +1,675 @@
+/*******************************************************************************
+ * 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.internal.callstack.core.flamegraph;
+
+import java.text.Format;
+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.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+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.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.analysis.timing.core.statistics.IStatistics;
+import org.eclipse.tracecompass.common.core.format.SubSecondTimeWithUnitFormat;
+import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
+import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils.FlowScopeLog;
+import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils.FlowScopeLogBuilder;
+import org.eclipse.tracecompass.incubator.analysis.core.concepts.AggregatedCallSite;
+import org.eclipse.tracecompass.incubator.analysis.core.concepts.ICallStackSymbol;
+import org.eclipse.tracecompass.incubator.analysis.core.model.IHostModel;
+import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.IWeightedTreeProvider;
+import org.eclipse.tracecompass.incubator.callstack.core.base.ICallStackElement;
+import org.eclipse.tracecompass.incubator.callstack.core.base.ICallStackGroupDescriptor;
+import org.eclipse.tracecompass.incubator.callstack.core.callgraph.AllGroupDescriptor;
+import org.eclipse.tracecompass.incubator.callstack.core.callgraph.CallGraph;
+import org.eclipse.tracecompass.incubator.callstack.core.callgraph.CallGraphGroupBy;
+import org.eclipse.tracecompass.incubator.callstack.core.callgraph.ICallGraphProvider;
+import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.callgraph.AggregatedThreadStatus;
+import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartEntryModel;
+import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartEntryModel.EntryType;
+import org.eclipse.tracecompass.internal.tmf.core.model.AbstractTmfTraceDataProvider;
+import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
+import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils;
+import org.eclipse.tracecompass.tmf.core.model.CommonStatusMessage;
+import org.eclipse.tracecompass.tmf.core.model.filters.SelectionTimeQueryFilter;
+import org.eclipse.tracecompass.tmf.core.model.filters.TimeQueryFilter;
+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.symbols.ISymbolProvider;
+import org.eclipse.tracecompass.tmf.core.symbols.SymbolProviderManager;
+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;
+
+/**
+ * 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
+ */
+@SuppressWarnings("restriction")
+public class FlameGraphDataProvider extends AbstractTmfTraceDataProvider implements ITimeGraphDataProvider<FlameChartEntryModel> {
+
+    /**
+     * Provider ID.
+     */
+    public static final String ID = "org.eclipse.tracecompass.incubator.internal.callstack.core.flamegraph.provider"; //$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$
+    private static final AtomicLong ENTRY_ID = new AtomicLong();
+    private static final Comparator<AggregatedCallSite> CCT_COMPARATOR = Comparator.comparingLong(AggregatedCallSite::getWeight).thenComparing(s -> String.valueOf(s.getObject()));
+    /**
+     * Logger for Abstract Tree Data Providers.
+     */
+    private static final Logger LOGGER = TraceCompassLog.getLogger(FlameGraphDataProvider.class);
+    private static final Format FORMATTER = SubSecondTimeWithUnitFormat.getInstance();
+
+    private final ICallGraphProvider fCgProvider;
+    private final String fAnalysisId;
+    private final long fTraceId = ENTRY_ID.getAndIncrement();
+
+    private final ReentrantReadWriteLock fLock = new ReentrantReadWriteLock(false);
+    private @Nullable Pair<Map<String, Object>, TmfModelResponse<TmfTreeModel<FlameChartEntryModel>>> fCached;
+    private final Map<Long, FlameChartEntryModel> fEntries = new HashMap<>();
+    private final Map<Long, CallGraphEntry> fCgEntries = new HashMap<>();
+    private final Collection<ISymbolProvider> fSymbolProviders;
+    private final Map<Long, Long> fEndTimes = new HashMap<>();
+
+    /** An internal class to describe the data for an entry */
+    private static class CallGraphEntry {
+        private final ICallStackElement fElement;
+        private final CallGraph fCallgraph;
+        private final int fDepth;
+
+        private CallGraphEntry(ICallStackElement element, CallGraph callgraph, int depth) {
+            fElement = element;
+            fCallgraph = callgraph;
+            fDepth = depth;
+        }
+    }
+
+    /**
+     * 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, ICallGraphProvider module, String secondaryId) {
+        super(trace);
+        fCgProvider = module;
+        fAnalysisId = secondaryId;
+        Collection<ISymbolProvider> symbolProviders = SymbolProviderManager.getInstance().getSymbolProviders(trace);
+        symbolProviders.forEach(provider -> provider.loadConfiguration(new NullProgressMonitor()));
+        fSymbolProviders = symbolProviders;
+    }
+
+    @Override
+    public String getId() {
+        return ID + ':' + fAnalysisId;
+    }
+
+    @Override
+    public @NonNull TmfModelResponse<@NonNull TmfTreeModel<@NonNull FlameChartEntryModel>> fetchTree(@NonNull Map<@NonNull String, @NonNull Object> fetchParameters, @Nullable IProgressMonitor monitor) {
+        // Did we cache this tree with those parameters
+        Pair<Map<String, Object>, TmfModelResponse<TmfTreeModel<FlameChartEntryModel>>> cached = fCached;
+        if (cached != null && cached.getFirst().equals(fetchParameters)) {
+            return cached.getSecond();
+        }
+        fLock.writeLock().lock();
+        try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameGraphDataProvider#fetchTree") //$NON-NLS-1$
+                .setCategory(getClass().getSimpleName()).build()) {
+
+            fEntries.clear();
+            fCgEntries.clear();
+            SubMonitor subMonitor = Objects.requireNonNull(SubMonitor.convert(monitor, "FlameGraphDataProvider#fetchRowModel", 2)); //$NON-NLS-1$
+
+            CallGraph callGraph = getCallGraph(fetchParameters, subMonitor);
+            if (subMonitor.isCanceled()) {
+                return new TmfModelResponse<>(null, ITmfResponse.Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED);
+            }
+            if (callGraph == null) {
+                return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.TASK_CANCELLED);
+            }
+
+            long start = 0;
+
+            // Initialize the first element of the tree
+            List<FlameChartEntryModel.Builder> builder = new ArrayList<>();
+            FlameChartEntryModel.Builder traceEntry = new FlameChartEntryModel.Builder(fTraceId, -1, getTrace().getName(), start, FlameChartEntryModel.EntryType.TRACE, -1);
+
+            buildCallGraphEntries(callGraph, builder, traceEntry);
+
+            ImmutableList.Builder<FlameChartEntryModel> 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<FlameChartEntryModel> tree = treeBuilder.build();
+
+            tree.forEach(entry -> {
+                fEntries.put(entry.getId(), entry);
+                fEndTimes.put(entry.getId(), entry.getEndTime());
+            });
+
+            TmfModelResponse<TmfTreeModel<FlameChartEntryModel>> response = new TmfModelResponse<>(new TmfTreeModel<>(Collections.emptyList(), tree),
+                    ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
+            fCached = new Pair<>(fetchParameters, response);
+            return response;
+
+        } finally {
+            fLock.writeLock().unlock();
+        }
+    }
+
+    private @Nullable CallGraph getCallGraph(Map<String, Object> fetchParameters, SubMonitor subMonitor) {
+        // Get the provider and wait for the analysis completion
+        ICallGraphProvider fcProvider = fCgProvider;
+        if (fcProvider instanceof IAnalysisModule) {
+            ((IAnalysisModule) fcProvider).waitForCompletion(subMonitor);
+        }
+        if (subMonitor.isCanceled()) {
+            return null;
+        }
+
+        // Get the full or selection callgraph
+        List<Long> selectionRange = DataProviderParameterUtils.extractLongList(fetchParameters, SELECTION_RANGE_KEY);
+        CallGraph callGraph;
+        if (selectionRange == null || selectionRange.size() != 2) {
+            callGraph = fcProvider.getCallGraph();
+        } else {
+            long time0 = selectionRange.get(0);
+            long time1 = selectionRange.get(1);
+            callGraph = fcProvider.getCallGraph(TmfTimestamp.fromNanos(Math.min(time0, time1)), TmfTimestamp.fromNanos(Math.max(time0, time1)));
+        }
+
+        // Look if we need to group the callgraph
+        ICallStackGroupDescriptor groupDescriptor = extractGroupDescriptor(fetchParameters, fcProvider);
+        if (groupDescriptor != null) {
+            callGraph = CallGraphGroupBy.groupCallGraphBy(groupDescriptor, callGraph);
+        }
+
+        return callGraph;
+    }
+
+    private static @Nullable ICallStackGroupDescriptor extractGroupDescriptor(Map<String, Object> fetchParameters, ICallGraphProvider 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
+        for (ICallStackGroupDescriptor groupDescriptor : fcProvider.getGroupDescriptors()) {
+            if (groupDescriptor.getName().equals(groupName)) {
+                return groupDescriptor;
+            }
+        }
+        return null;
+    }
+
+    private void buildCallGraphEntries(CallGraph callgraph, List<FlameChartEntryModel.Builder> builder, FlameChartEntryModel.Builder traceEntry) {
+        Collection<ICallStackElement> elements = callgraph.getElements();
+        for (ICallStackElement element : elements) {
+            buildChildrenEntries(element, callgraph, builder, traceEntry);
+        }
+
+    }
+
+    /**
+     * Build the entry list for one thread
+     *
+     * @param element
+     *            The node of the aggregation tree
+     */
+    private void buildChildrenEntries(ICallStackElement element, CallGraph callgraph, List<FlameChartEntryModel.Builder> builder, FlameChartEntryModel.Builder parent) {
+        // Add the entry
+        FlameChartEntryModel.Builder entry = new FlameChartEntryModel.Builder(ENTRY_ID.getAndIncrement(), parent.getId(), element.getName(), 0, FlameChartEntryModel.EntryType.LEVEL, -1);
+        builder.add(entry);
+
+        // Create the children entries
+        for (ICallStackElement child : element.getChildrenElements()) {
+            buildChildrenEntries(child, 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);
+
+        // Create the function callsite entries
+        if (!(element.isLeaf())) {
+            return;
+        }
+
+        List<FlameChartEntryModel.Builder> childrenEntries = new ArrayList<>();
+        List<FlameChartEntryModel.Builder> extraEntries = new ArrayList<>();
+        Deque<Long> timestampStack = new ArrayDeque<>();
+        timestampStack.push(0L);
+
+        // Sort children by duration
+        List<AggregatedCallSite> rootFunctions = new ArrayList<>(callgraph.getCallingContextTree(element));
+        rootFunctions.sort(CCT_COMPARATOR);
+        for (AggregatedCallSite rootFunction : rootFunctions) {
+            createLevelChildren(element, rootFunction, childrenEntries, timestampStack, entry.getId());
+            createExtraChildren(rootFunction, extraEntries, timestampStack, entry.getId());
+            long currentThreadDuration = timestampStack.pop() + rootFunction.getWeight();
+            timestampStack.push(currentThreadDuration);
+        }
+        for (FlameChartEntryModel.Builder child : childrenEntries) {
+            builder.add(child);
+            fCgEntries.put(child.getId(), new CallGraphEntry(element, callgraph, child.getDepth()));
+        }
+        for (FlameChartEntryModel.Builder child : extraEntries) {
+            builder.add(child);
+            fCgEntries.put(child.getId(), new CallGraphEntry(element, callgraph, child.getDepth()));
+        }
+        entry.setEndTime(timestampStack.pop());
+        return;
+    }
+
+    /**
+     * Parse the aggregated tree created by the callGraphAnalysis and creates
+     * the event list (functions) for each entry (depth)
+     *
+     * @param element
+     *
+     * @param firstNode
+     *            The first node of the aggregation tree
+     * @param childrenEntries
+     *            The list of entries for one thread
+     * @param timestampStack
+     *            A stack used to save the functions timeStamps
+     */
+    private static void createLevelChildren(ICallStackElement element, AggregatedCallSite firstNode, List<FlameChartEntryModel.Builder> childrenEntries, Deque<Long> timestampStack, long parentId) {
+        Long lastEnd = timestampStack.peek();
+        if (lastEnd == null) {
+            return;
+        }
+        // Prepare all the level entries for this callsite
+        for (int i = 0; i <= firstNode.getMaxDepth() - 1; i++) {
+            if (i >= childrenEntries.size()) {
+                FlameChartEntryModel.Builder entry = new FlameChartEntryModel.Builder(ENTRY_ID.getAndIncrement(), parentId, String.valueOf(i), 0, EntryType.FUNCTION, i);
+                childrenEntries.add(entry);
+            }
+            childrenEntries.get(i).setEndTime(lastEnd + firstNode.getWeight());
+        }
+    }
+
+    private static void createExtraChildren(AggregatedCallSite firstNode, List<FlameChartEntryModel.Builder> extraEntries, Deque<Long> timestampStack, long parentId) {
+        Long lastEnd = timestampStack.peek();
+        if (lastEnd == null) {
+            return;
+        }
+        Iterator<AggregatedCallSite> extraChildrenSites = firstNode.getExtraChildrenSites().iterator();
+
+        if (!extraChildrenSites.hasNext()) {
+            return;
+        }
+        // Get or add the entry
+        if (extraEntries.isEmpty()) {
+            FlameChartEntryModel.Builder entry = new FlameChartEntryModel.Builder(ENTRY_ID.getAndIncrement(), parentId, Objects.requireNonNull(Messages.FlameGraph_KernelStatusTitle), 0, EntryType.KERNEL, -1);
+            extraEntries.add(entry);
+        }
+        FlameChartEntryModel.Builder entry = extraEntries.get(0);
+
+        while (extraChildrenSites.hasNext()) {
+            AggregatedCallSite next = extraChildrenSites.next();
+            lastEnd += next.getWeight();
+            entry.setEndTime(lastEnd);
+        }
+    }
+
+    @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<Long> times = DataProviderParameterUtils.extractTimeRequested(fetchParameters);
+        if (times == null) {
+            return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.INCORRECT_QUERY_PARAMETERS);
+        }
+        List<ITimeGraphRowModel> rowModels = new ArrayList<>();
+
+        // Get the selected entries
+        Collection<Long> selected = DataProviderParameterUtils.extractSelectedItems(fetchParameters);
+        if (selected == null) {
+            // No entry selected, assume all
+            selected = fEntries.keySet();
+        }
+        List<CallGraphEntry> selectedEntries = new ArrayList<>();
+        Multimap<Pair<CallGraph, ICallStackElement>, Pair<Integer, Long>> requested = HashMultimap.create();
+        for (Long id : selected) {
+            CallGraphEntry entry = fCgEntries.get(id);
+            if (entry != null) {
+                selectedEntries.add(entry);
+                requested.put(new Pair<>(entry.fCallgraph, entry.fElement), new Pair<>(entry.fDepth, 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
+        for (Pair<CallGraph, ICallStackElement> element : requested.keySet()) {
+            if (subMonitor.isCanceled()) {
+                return new TmfModelResponse<>(null, ITmfResponse.Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED);
+            }
+            Collection<Pair<Integer, Long>> depths = requested.get(element);
+            rowModels.addAll(getStatesForElement(times, predicates, subMonitor, element.getFirst(), element.getSecond(), depths));
+        }
+
+        return new TmfModelResponse<>(new TimeGraphModel(rowModels), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
+    }
+
+    private List<ITimeGraphRowModel> getStatesForElement(List<Long> times, Map<Integer, Predicate<Multimap<String, Object>>> predicates, IProgressMonitor monitor,
+            CallGraph callgraph, ICallStackElement csElement,
+            Collection<Pair<Integer, Long>> depths) {
+        // Get the cct for this element (first level callsites) and sort them
+        Collection<AggregatedCallSite> cct = callgraph.getCallingContextTree(csElement);
+        List<AggregatedCallSite> sortedCct = new ArrayList<>(cct);
+        sortedCct.sort(CCT_COMPARATOR);
+
+        // Maps a depth with a pair of entry ID and list of states
+        Map<Integer, Pair<Long, List<ITimeGraphState>>> depthIds = new HashMap<>();
+        int maxDepth = 0;
+        long maxEndTime = 0;
+        for (Pair<Integer, Long> depth : depths) {
+            maxDepth = Math.max(depth.getFirst(), maxDepth);
+            Long endTime = fEndTimes.get(depth.getSecond());
+            maxEndTime = endTime != null ? Math.max(endTime, maxEndTime) : maxEndTime;
+            depthIds.put(depth.getFirst(), new Pair<>(depth.getSecond(), new ArrayList<>()));
+        }
+
+        long currentWeightTime = 0;
+        Pair<Long, List<ITimeGraphState>> kernelData = depthIds.get(-1);
+        // Start parsing the callgraph
+        for (AggregatedCallSite callsite : sortedCct) {
+            if (timeOverlap(currentWeightTime, callsite.getWeight(), times)) {
+                recurseAddCallsite(callsite, currentWeightTime, predicates, times, depthIds, 0, maxDepth, monitor);
+
+                // Get the kernel data if necessary
+                if (kernelData != null) {
+                    List<AggregatedCallSite> extraChildrenSites = new ArrayList<>(callsite.getExtraChildrenSites());
+                    extraChildrenSites.sort(CCT_COMPARATOR);
+                    // Add the required children
+                    long weightTime = currentWeightTime;
+                    for (AggregatedCallSite child : extraChildrenSites) {
+                        if (timeOverlap(weightTime, child.getWeight(), times)) {
+                            ITimeGraphState timeGraphState = new TimeGraphState(weightTime, child.getWeight(), ((AggregatedThreadStatus) child).getProcessStatus().getStateValue().unboxInt());
+                            applyFilterAndAddState(kernelData.getSecond(), timeGraphState, kernelData.getFirst(), predicates, monitor);
+
+                        }
+                        weightTime += child.getWeight();
+                    }
+                }
+            }
+            currentWeightTime += callsite.getWeight();
+        }
+
+        // We may need to fill with null after the last callsite
+        if (maxEndTime > currentWeightTime && timeOverlap(currentWeightTime, maxEndTime - currentWeightTime, times)) {
+            fillDeeperWithNull(0, maxDepth, depthIds, currentWeightTime, maxEndTime - currentWeightTime);
+        }
+
+        List<ITimeGraphRowModel> rowModels = new ArrayList<>();
+        for (Pair<Long, List<ITimeGraphState>> states : depthIds.values()) {
+            rowModels.add(new TimeGraphRowModel(states.getFirst(), states.getSecond()));
+        }
+        return rowModels;
+    }
+
+    // Recursively adds this callsite states and its children
+    private void recurseAddCallsite(AggregatedCallSite callsite, long stateStartTime,
+            Map<Integer, Predicate<Multimap<String, Object>>> predicates,
+            List<Long> times, Map<Integer, Pair<Long, List<ITimeGraphState>>> depthIds,
+            int depth, int maxDepth, IProgressMonitor monitor) {
+        if (monitor.isCanceled()) {
+            return;
+        }
+        // Add the state if current depth is requested
+        Pair<Long, List<ITimeGraphState>> stateList = depthIds.get(depth);
+        if (stateList != null) {
+            ITimeGraphState timeGraphState = createTimeGraphState(callsite, stateStartTime);
+            applyFilterAndAddState(stateList.getSecond(), timeGraphState, stateList.getFirst(), predicates, monitor);
+        }
+        // Stop recursing if there's no more depth requested or if depth is -1
+        if (depth >= maxDepth) {
+            return;
+        }
+
+        /* We can fill with null states all depth deeper than the current site's max depth. Max depth includes the current element, so we -1 */
+        int thisMaxDepth = depth + callsite.getMaxDepth() - 1;
+        fillDeeperWithNull(thisMaxDepth, maxDepth, depthIds, stateStartTime, callsite.getWeight());
+
+        // Get and sort the children
+        List<AggregatedCallSite> children = new ArrayList<>(callsite.getCallees());
+        if (children.isEmpty()) {
+            return;
+        }
+        children.sort(CCT_COMPARATOR);
+
+        // Add the required children
+        long weightTime = stateStartTime;
+        for (AggregatedCallSite child : children) {
+            if (timeOverlap(weightTime, child.getWeight(), times)) {
+                recurseAddCallsite(child, weightTime, predicates, times, depthIds, depth + 1, thisMaxDepth, monitor);
+            }
+            weightTime += child.getWeight();
+        }
+        // We may need to fill the remaining data with null states
+        if (callsite.getWeight() > weightTime - stateStartTime && timeOverlap(weightTime, callsite.getWeight() - (weightTime - stateStartTime), times)) {
+            fillDeeperWithNull(depth, thisMaxDepth, depthIds, weightTime, callsite.getWeight() - (weightTime - stateStartTime));
+        }
+
+    }
+
+    private static void fillDeeperWithNull(int depth, int depthLimit, Map<Integer, Pair<Long, List<ITimeGraphState>>> depthIds, long time, long duration) {
+        if (depthLimit <= depth) {
+            return;
+        }
+        /* Fill with null time graph states all entries deeper than depth */
+        for (Entry<Integer, Pair<Long, List<ITimeGraphState>>> depthEntry : depthIds.entrySet()) {
+            if (depthEntry.getKey() > depth && depthEntry.getKey() <= depthLimit) {
+                depthEntry.getValue().getSecond().add(new TimeGraphState(time, duration, Integer.MIN_VALUE));
+            }
+        }
+    }
+
+    private ITimeGraphState createTimeGraphState(AggregatedCallSite callsite, long currentWeightTime) {
+        ICallStackSymbol value = callsite.getObject();
+        String resolved = value.resolve(fSymbolProviders);
+        return new TimeGraphState(currentWeightTime, callsite.getWeight(), value.hashCode(), resolved);
+    }
+
+    /** Verify if one of the requested time overlaps this callsite */
+    private static boolean timeOverlap(long start, long duration, List<Long> times) {
+        long end = start + duration;
+        for (Long time : times) {
+            if (time >= start && time <= end) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @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<Long> 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<Long> 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);
+        CallGraphEntry callGraphEntry = fCgEntries.get(item);
+        if (callGraphEntry == null) {
+            return new TmfModelResponse<>(Collections.emptyMap(), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
+        }
+        AggregatedCallSite callSite = findCallSite(callGraphEntry.fCallgraph.getCallingContextTree(callGraphEntry.fElement), time, callGraphEntry.fDepth, 0, 0);
+        if (callSite != null) {
+            return new TmfModelResponse<>(getTooltip(callSite), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
+        }
+
+        return new TmfModelResponse<>(Collections.emptyMap(), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
+    }
+
+    private static Map<String, String> getTooltip(AggregatedCallSite callSite) {
+        ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
+        for (Entry<String, IStatistics<?>> entry : callSite.getStatistics().entrySet()) {
+            String statType = String.valueOf(entry.getKey());
+            IStatistics<?> stats = entry.getValue();
+            if (stats.getMax() != IHostModel.TIME_UNKNOWN) {
+                builder.put(statType, ""); //$NON-NLS-1$
+                String lowerType = statType.toLowerCase();
+                builder.put("\t" + Messages.FlameGraph_Total + ' ' + lowerType, FORMATTER.format(stats.getTotal())); //$NON-NLS-1$
+                builder.put("\t" + Messages.FlameGraph_Average + ' ' + lowerType, FORMATTER.format(stats.getMean())); //$NON-NLS-1$
+                builder.put("\t" + Messages.FlameGraph_Max + ' ' + lowerType, FORMATTER.format(stats.getMax())); //$NON-NLS-1$
+                builder.put("\t" + Messages.FlameGraph_Min + ' ' + lowerType, FORMATTER.format(stats.getMin())); //$NON-NLS-1$
+                builder.put("\t" + Messages.FlameGraph_Deviation + ' ' + lowerType, FORMATTER.format(stats.getStdDev())); //$NON-NLS-1$
+
+            }
+        }
+        return builder.build();
+    }
+
+    /** Find the callsite at the time and depth requested */
+    private static @Nullable AggregatedCallSite findCallSite(Collection<AggregatedCallSite> collection, Long time, int depth, long currentTime, int currentDepth) {
+        List<AggregatedCallSite> cct = new ArrayList<>(collection);
+        cct.sort(CCT_COMPARATOR);
+        long weight = currentTime;
+        for (AggregatedCallSite callsite : cct) {
+            if (weight + callsite.getWeight() < time) {
+                weight += callsite.getWeight();
+                continue;
+            }
+            // This is the right callsite, let's check the depth
+            if (currentDepth == depth) {
+                return callsite;
+            }
+            return findCallSite(callsite.getCallees(), time, depth, weight, currentDepth + 1);
+        }
+        return null;
+    }
+
+    @Deprecated
+    @Override
+    public TmfModelResponse<List<FlameChartEntryModel>> fetchTree(@NonNull TimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
+        Map<String, Object> parameters = FetchParametersUtils.timeQueryToMap(filter);
+        TmfModelResponse<@NonNull TmfTreeModel<@NonNull FlameChartEntryModel>> response = fetchTree(parameters, monitor);
+        TmfTreeModel<@NonNull FlameChartEntryModel> model = response.getModel();
+        List<FlameChartEntryModel> treeModel = null;
+        if (model != null) {
+            treeModel = model.getEntries();
+        }
+        return new TmfModelResponse<>(treeModel, response.getStatus(),
+                response.getStatusMessage());
+    }
+
+    @Deprecated
+    @Override
+    public TmfModelResponse<List<ITimeGraphRowModel>> fetchRowModel(@NonNull SelectionTimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
+        Map<String, Object> parameters = FetchParametersUtils.selectionTimeQueryToMap(filter);
+        TmfModelResponse<TimeGraphModel> response = fetchRowModel(parameters, monitor);
+        TimeGraphModel model = response.getModel();
+        List<ITimeGraphRowModel> rows = null;
+        if (model != null) {
+            rows = model.getRows();
+        }
+        return new TmfModelResponse<>(rows, response.getStatus(), response.getStatusMessage());
+    }
+
+    @Deprecated
+    @Override
+    public @NonNull TmfModelResponse<@NonNull List<@NonNull ITimeGraphArrow>> fetchArrows(@NonNull TimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
+        Map<String, Object> parameters = FetchParametersUtils.timeQueryToMap(filter);
+        return fetchArrows(parameters, monitor);
+    }
+
+    @Deprecated
+    @Override
+    public @NonNull TmfModelResponse<@NonNull Map<@NonNull String, @NonNull String>> fetchTooltip(@NonNull SelectionTimeQueryFilter filter, @Nullable IProgressMonitor monitor) {
+        Map<String, Object> parameters = FetchParametersUtils.selectionTimeQueryToMap(filter);
+        return fetchTooltip(parameters, monitor);
+    }
+
+}
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/FlameGraphDataProviderFactory.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/FlameGraphDataProviderFactory.java
new file mode 100644
index 0000000..3bb17cf
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/FlameGraphDataProviderFactory.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2018 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.incubator.internal.callstack.core.flamegraph;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.callstack.core.callgraph.ICallGraphProvider;
+import org.eclipse.tracecompass.internal.tmf.core.model.xy.TmfTreeXYCompositeDataProvider;
+import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.dataprovider.IDataProviderFactory;
+import org.eclipse.tracecompass.tmf.core.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 org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
+
+import com.google.common.collect.Iterables;
+
+/**
+ * Factory for the flame chart data provider
+ *
+ * @author Geneviève Bastien
+ */
+public class FlameGraphDataProviderFactory implements IDataProviderFactory {
+
+    @Override
+    public @Nullable ITmfTreeDataProvider<? extends ITmfTreeDataModel> createProvider(ITmfTrace trace) {
+        // Need the analysis
+        return null;
+    }
+
+    @Override
+    public @Nullable ITmfTreeDataProvider<? extends ITmfTreeDataModel> createProvider(ITmfTrace trace, String secondaryId) {
+        // Create with the trace or experiment first
+        ITmfTreeDataProvider<? extends ITmfTreeDataModel> provider = create(trace, secondaryId);
+        if (provider != null) {
+            return provider;
+        }
+        // Otherwise, see if it's an experiment and create a composite if that's the
+        // case
+        Collection<ITmfTrace> traces = TmfTraceManager.getTraceSet(trace);
+        if (traces.size() > 1) {
+            // Try creating a composite only if there are many traces, otherwise, the
+            // previous call to create should have returned the data provider
+            return TmfTreeXYCompositeDataProvider.create(traces, Objects.requireNonNull(Messages.FlameGraphDataProvider_Title), FlameGraphDataProvider.ID, secondaryId);
+        }
+        return null;
+
+    }
+
+    private static @Nullable ITmfTreeDataProvider<? extends ITmfTreeDataModel> create(ITmfTrace trace, String secondaryId) {
+        // The trace can be an experiment, so we need to know if there are multiple
+        // analysis modules with the same ID
+        Iterable<ICallGraphProvider> modules = TmfTraceUtils.getAnalysisModulesOfClass(trace, ICallGraphProvider.class);
+        Iterable<ICallGraphProvider> filteredModules = Iterables.filter(modules, m -> ((IAnalysisModule) m).getId().equals(secondaryId));
+        Iterator<ICallGraphProvider> iterator = filteredModules.iterator();
+        if (iterator.hasNext()) {
+            ICallGraphProvider module = iterator.next();
+            if (iterator.hasNext()) {
+                // More than one module, must be an experiment, return null so the factory can
+                // try with individual traces
+                return null;
+            }
+            ((IAnalysisModule) module).schedule();
+            return new FlameGraphDataProvider(trace, module, secondaryId);
+        }
+        return null;
+    }
+
+}
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/FlameGraphEntryModel.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/FlameGraphEntryModel.java
new file mode 100644
index 0000000..0d41a2c
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/FlameGraphEntryModel.java
@@ -0,0 +1,14 @@
+/*******************************************************************************
+ * 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.internal.callstack.core.flamegraph;
+
+public class FlameGraphEntryModel {
+
+}
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/Messages.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/Messages.java
new file mode 100644
index 0000000..f7a1299
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/Messages.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+package org.eclipse.tracecompass.incubator.internal.callstack.core.flamegraph;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+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$
+    /** Title of the data provider */
+    public static String FlameGraphDataProvider_Title;
+    /** Label for the symbol */
+    public static String FlameGraph_Symbol;
+    /** Label for the CPU times */
+    public static String FlameGraph_Total;
+    /** Label for total CPU time */
+    public static String FlameGraph_Average;
+    /** Label for average CPU time */
+    public static String FlameGraph_Min;
+    /** Label for minimum CPU time */
+    public static String FlameGraph_Max;
+    /** Label for maximum CPU time */
+    public static String FlameGraph_Deviation;
+    /**
+     * The number of calls of a function
+     */
+    public static String FlameGraph_NbCalls;
+    /**
+     * The depth of a function
+     */
+    public static String FlameGraph_Depth;
+    /**
+     * Percentage text
+     */
+    public static String FlameGraph_Percentage;
+
+    /** Title of kernel status rows */
+    public static @Nullable String FlameGraph_KernelStatusTitle;
+
+    static {
+        // initialize resource bundle
+        NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+    }
+
+    private Messages() {
+    }
+}
\ No newline at end of file
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/messages.properties b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/messages.properties
new file mode 100644
index 0000000..46edba7
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/messages.properties
@@ -0,0 +1,20 @@
+###############################################################################
+# 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
+###############################################################################
+
+FlameGraphDataProvider_Title=FlameGraph
+FlameGraph_Symbol=Symbol
+FlameGraph_Total=Total
+FlameGraph_Average=Mean
+FlameGraph_Min=Minimum
+FlameGraph_Max=Maximum
+FlameGraph_Deviation=Deviation
+FlameGraph_Percentage=Percentage
+FlameGraph_NbCalls=Number of calls
+FlameGraph_Depth=Depth
+FlameGraph_KernelStatusTitle=Kernel
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/package-info.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/package-info.java
new file mode 100644
index 0000000..d2d0854
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/flamegraph/package-info.java
@@ -0,0 +1,11 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Ericsson
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.incubator.internal.callstack.core.flamegraph;
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartEntryModel.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartEntryModel.java
index f593fb2..47c4089 100644
--- a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartEntryModel.java
+++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartEntryModel.java
@@ -9,6 +9,7 @@
 
 package org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -31,7 +32,7 @@
     /**
      * An enumeration for the type of flame chart entries
      */
-    public enum EntryType {
+    public static enum EntryType {
         /**
          * A descriptive entry, for example the one for the trace
          */
@@ -45,12 +46,129 @@
          */
         FUNCTION,
         /**
-         * This entry will show the kernel statuses for the TID running the callstack.
-         * Will not always be present
+         * This entry will show the kernel statuses for the TID running the
+         * callstack. Will not always be present
          */
         KERNEL
     }
 
+    /**
+     * A builder class for this entry model
+     */
+    public static class Builder {
+        private final long fId;
+        private final long fParentId;
+        private final long fStartTime;
+        private final String fName;
+        private final EntryType fEntryType;
+        private final int fDepth;
+        private long fEndTime;
+        private @Nullable HostThread fHostThread;
+
+        /**
+         * Constructor
+         *
+         * @param id
+         *            The unique ID for this Entry model for its trace
+         * @param parentId
+         *            The unique ID of the parent entry model
+         * @param name
+         *            The name of this entry
+         * @param start
+         *            the thread's start time
+         * @param entryType
+         *            The type of this entry
+         * @param depth
+         *            The depth if the entry is a function entry, can be
+         *            <code>-1</code> if no depth
+         */
+        public Builder(long id, long parentId, String name, long start, EntryType entryType, int depth) {
+            fId = id;
+            fParentId = parentId;
+            fName = name;
+            fStartTime = start;
+            fEntryType = entryType;
+            fDepth = depth;
+            fEndTime = start;
+            fHostThread = null;
+        }
+
+        /**
+         * Get the unique ID for this entry / builder
+         *
+         * @return this entry's unique ID
+         */
+        public long getId() {
+            return fId;
+        }
+
+        /**
+         * Get this entry / builder's start time
+         *
+         * @return the start time
+         */
+        public long getStartTime() {
+            return fStartTime;
+        }
+
+        /**
+         * Get this entry/builder's end time
+         *
+         * @return the end time
+         */
+        public long getEndTime() {
+            return fEndTime;
+        }
+
+        /**
+         * Update this entry / builder's end time
+         *
+         * @param endTime
+         *            the new end time
+         */
+        public void setEndTime(long endTime) {
+            fEndTime = Long.max(fEndTime, endTime);
+        }
+
+        /**
+         * Update this entry / builder's HostThread
+         *
+         * @param hostThread
+         *            the new HostThread
+         */
+        public void setHostThread(@Nullable HostThread hostThread) {
+            fHostThread = hostThread;
+        }
+
+        /**
+         * Build the {@link FlameChartEntryModel} from the builder, specify the
+         * parent id here to avoid race conditions
+         *
+         * @return the relevant {@link FlameChartEntryModel}
+         */
+        public FlameChartEntryModel build() {
+            return new FlameChartEntryModel(fId, fParentId, Collections.singletonList(fName), fStartTime, fEndTime, fEntryType, fDepth, fHostThread);
+        }
+
+        /**
+         * Get the parent ID of this entry
+         *
+         * @return The parent ID
+         */
+        public long getParentId() {
+            return fParentId;
+        }
+
+        /**
+         * Get the depth of this entry
+         *
+         * @return The depth
+         */
+        public int getDepth() {
+            return fDepth;
+        }
+    }
+
     private final EntryType fEntryType;
     private final int fDepth;
     private @Nullable HostThread fHostThread;