callstack.core: Add support for multiple depth callstack

Signed-off-by: Arnaud Fiorini <fiorini.arnaud@gmail.com>
Change-Id: I42e0eaf4d7c2cd8bbae6992905c82319ebafca4a
Reviewed-on: https://git.eclipse.org/r/c/tracecompass/org.eclipse.tracecompass/+/201475
Tested-by: Trace Compass Bot <tracecompass-bot@eclipse.org>
Tested-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Reviewed-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Reviewed-by: Patrick Tasse <patrick.tasse@gmail.com>
diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/src/org/eclipse/tracecompass/analysis/callstack/core/tests/FlameChartDataProviderTest.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/src/org/eclipse/tracecompass/analysis/callstack/core/tests/FlameChartDataProviderTest.java
index 066872a..14d86de 100644
--- a/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/src/org/eclipse/tracecompass/analysis/callstack/core/tests/FlameChartDataProviderTest.java
+++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core.tests/src/org/eclipse/tracecompass/analysis/callstack/core/tests/FlameChartDataProviderTest.java
@@ -107,7 +107,7 @@
         TmfTreeModel<@NonNull FlameChartEntryModel> model = responseTree.getModel();
         assertNotNull(model);
         List<@NonNull FlameChartEntryModel> modelEntries = model.getEntries();
-        assertEquals(26, modelEntries.size());
+        assertEquals(22, modelEntries.size());
 
         String traceName = getTrace().getName();
 
diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/base/ICallStackElement.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/base/ICallStackElement.java
index b7da719..58e618f 100644
--- a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/base/ICallStackElement.java
+++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/base/ICallStackElement.java
@@ -99,4 +99,11 @@
      *         not
      */
     boolean isLeaf();
+
+    /**
+     * Get whether this element has a callstack attribute under it.
+     *
+     * @return true if this element has a callstack, false otherwise
+     */
+    boolean isCallStack();
 }
diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/callgraph/CallGraphAnalysis.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/callgraph/CallGraphAnalysis.java
index 06d95fe..012095e 100644
--- a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/callgraph/CallGraphAnalysis.java
+++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/callgraph/CallGraphAnalysis.java
@@ -208,9 +208,11 @@
 
     private void iterateOverElement(ICallStackElement element, IHostModel model, CallGraph callgraph, long start, long end, IProgressMonitor monitor) {
         // Iterator over the children of the element until we reach the leaves
-        if (element.isLeaf()) {
+        if ((element instanceof InstrumentedCallStackElement) && ((InstrumentedCallStackElement) element).isCallStack()) {
             iterateOverLeafElement(element, model, callgraph, start, end, monitor);
-            return;
+            if (element.isLeaf()) {
+                return;
+            }
         }
         for (ICallStackElement child : element.getChildrenElements()) {
             iterateOverElement(child, model, callgraph, start, end, monitor);
diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/callstack/CallStackElement.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/callstack/CallStackElement.java
index 090f066..ea45233 100644
--- a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/callstack/CallStackElement.java
+++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/callstack/CallStackElement.java
@@ -111,6 +111,11 @@
     }
 
     @Override
+    public boolean isCallStack() {
+        return isLeaf();
+    }
+
+    @Override
     public @Nullable IWeightedTreeGroupDescriptor getNextGroup() {
         return fNextDescriptor;
     }
diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/callstack/CallStackSeries.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/callstack/CallStackSeries.java
index 8259821..66d3a30 100644
--- a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/callstack/CallStackSeries.java
+++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/callstack/CallStackSeries.java
@@ -399,7 +399,7 @@
         }
     }
 
-    private final InstrumentedGroupDescriptor fRootGroup;
+    private InstrumentedGroupDescriptor fRootGroup;
     private final String fName;
     private final @Nullable IThreadIdResolver fResolver;
     private final IHostIdResolver fHostResolver;
@@ -427,21 +427,11 @@
      *            The thread resolver
      */
     public CallStackSeries(ITmfStateSystem ss, List<String[]> patternPaths, int symbolKeyLevelIndex, String name, IHostIdResolver hostResolver, @Nullable IThreadIdResolver threadResolver) {
-        // Build the groups from the state system and pattern paths
-        if (patternPaths.isEmpty()) {
-            throw new IllegalArgumentException("State system callstack: the list of paths should not be empty"); //$NON-NLS-1$
-        }
-        int startIndex = patternPaths.size() - 1;
-        InstrumentedGroupDescriptor prevLevel = new InstrumentedGroupDescriptor(ss, patternPaths.get(startIndex), null, symbolKeyLevelIndex == startIndex);
-        for (int i = startIndex - 1; i >= 0; i--) {
-            InstrumentedGroupDescriptor level = new InstrumentedGroupDescriptor(ss, patternPaths.get(i), prevLevel, symbolKeyLevelIndex == i);
-            prevLevel = level;
-        }
         fStateSystem = ss;
-        fRootGroup = prevLevel;
         fName = name;
         fResolver = threadResolver;
         fHostResolver = hostResolver;
+        fRootGroup = createRootGroup(patternPaths, symbolKeyLevelIndex);
     }
 
     /**
@@ -463,6 +453,34 @@
     }
 
     /**
+     * @param patternPaths
+     *            The patterns for the different levels of the callstack in the
+     *            state system. Any further level path is relative to the
+     *            previous one.
+     * @param symbolKeyLevelIndex
+     *            The index in the list of the list to be used as a key to the
+     *            symbol provider. The data at this level must be an integer,
+     *            for instance a process ID
+     */
+    public void updateRootGroup(List<String[]> patternPaths, int symbolKeyLevelIndex) {
+        fRootGroup = createRootGroup(patternPaths, symbolKeyLevelIndex);
+    }
+
+    private InstrumentedGroupDescriptor createRootGroup(List<String[]> patternPaths, int symbolKeyLevelIndex) {
+        // Build the groups from the state system and pattern paths
+        if (patternPaths.isEmpty()) {
+            throw new IllegalArgumentException("State system callstack: the list of paths should not be empty"); //$NON-NLS-1$
+        }
+        int startIndex = patternPaths.size() - 1;
+        InstrumentedGroupDescriptor prevLevel = new InstrumentedGroupDescriptor(fStateSystem, patternPaths.get(startIndex), null, symbolKeyLevelIndex == startIndex);
+        for (int i = startIndex - 1; i >= 0; i--) {
+            InstrumentedGroupDescriptor level = new InstrumentedGroupDescriptor(fStateSystem, patternPaths.get(i), prevLevel, symbolKeyLevelIndex == i);
+            prevLevel = level;
+        }
+        return prevLevel;
+    }
+
+    /**
      * Get the name of this callstack series
      *
      * @return The name of the callstack series
@@ -523,12 +541,15 @@
     // Segment store methods
     // ---------------------------------------------------
 
-    private static Collection<ICallStackElement> getLeafElements(ICallStackElement element) {
+    private static Collection<ICallStackElement> getCallStacks(ICallStackElement element) {
         if (element.isLeaf()) {
             return Collections.singleton(element);
         }
         List<ICallStackElement> list = new ArrayList<>();
-        element.getChildrenElements().forEach(e -> list.addAll(getLeafElements(e)));
+        if (element.isCallStack()) {
+            list.add(element);
+        }
+        element.getChildrenElements().forEach(e -> list.addAll(getCallStacks(e)));
         return list;
     }
 
@@ -632,8 +653,8 @@
     private Map<Integer, CallStack> getCallStackQuarks() {
         Map<Integer, CallStack> quarkToElement = new HashMap<>();
         // Get the leaf elements and their callstacks
-        getRootElements().stream().flatMap(e -> getLeafElements(e).stream())
-                .filter(e -> e instanceof InstrumentedCallStackElement)
+        getRootElements().stream().flatMap(e -> getCallStacks(e).stream())
+                .filter(InstrumentedCallStackElement.class::isInstance)
                 .map(e -> (InstrumentedCallStackElement) e)
                 .forEach(e -> e.getStackQuarks().forEach(c -> quarkToElement.put(c, e.getCallStack())));
         return quarkToElement;
diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartDataProvider.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartDataProvider.java
index 2c37b50..8d7f035 100644
--- a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartDataProvider.java
+++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartDataProvider.java
@@ -343,7 +343,7 @@
             boolean complete = fcProvider.isComplete();
             CallStackSeries callstack = fcProvider.getCallStackSeries();
             if (callstack == null) {
-                return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.ANALYSIS_INITIALIZATION_FAILED);
+                return new TmfModelResponse<>(new TmfTreeModel<>(Collections.emptyList(), Collections.emptyList()), ITmfResponse.Status.RUNNING, CommonStatusMessage.RUNNING);
             }
             long start = getTrace().getStartTime().getValue();
             long end = Math.max(start, fcProvider.getEnd());
@@ -351,10 +351,10 @@
             // Initialize the first element of the tree
             ImmutableList.Builder<FlameChartEntryModel> builder = ImmutableList.builder();
             @SuppressWarnings("null")
-            FlameChartEntryModel traceEntry = new FlameChartEntryModel(fTraceId, -1, Collections.singletonList(getTrace().getName()), start, end, FlameChartEntryModel.EntryType.TRACE);
+            FlameChartEntryModel traceEntry = new FlameChartEntryModel(fTraceId, -1, getTrace().getName(), start, end, FlameChartEntryModel.EntryType.TRACE);
             builder.add(traceEntry);
-
             FlameChartEntryModel callStackRoot = traceEntry;
+
             // If there is more than one callstack objects in the analysis,
             // create a root per series
             for (ICallStackElement element : callstack.getRootElements()) {
@@ -384,36 +384,34 @@
         long elementId = getEntryId(element);
 
         // Is this an intermediate or leaf element
-        if ((element instanceof InstrumentedCallStackElement) && element.isLeaf()) {
+        if (element instanceof InstrumentedCallStackElement && ((InstrumentedCallStackElement) element).isCallStack()) {
             // For the leaf element, add the callstack entries
             InstrumentedCallStackElement finalElement = (InstrumentedCallStackElement) element;
             CallStack callStack = finalElement.getCallStack();
             // Set the fixed hostThread to the entry if it is available
             HostThread hostThread = callStack.getHostThread();
             // Create the entry for this level
-            FlameChartEntryModel entry = new FlameChartEntryModel(elementId, parentEntry.getId(), Collections.singletonList(element.getName()), parentEntry.getStartTime(), parentEntry.getEndTime(), FlameChartEntryModel.EntryType.LEVEL, -1,
-                    hostThread);
+            FlameChartEntryModel entry = new FlameChartEntryModel(elementId, parentEntry.getId(), element.getName(),
+                    parentEntry.getStartTime(), parentEntry.getEndTime(), FlameChartEntryModel.EntryType.LEVEL, -1, hostThread);
             builder.add(entry);
             for (int depth = 0; depth < callStack.getMaxDepth(); depth++) {
-                FlameChartEntryModel flameChartEntry = new FlameChartEntryModel(getEntryId(new CallStackDepth(callStack, depth + 1)), entry.getId(), Collections.singletonList(element.getName()), parentEntry.getStartTime(), parentEntry.getEndTime(),
+                FlameChartEntryModel flameChartEntry = new FlameChartEntryModel(getEntryId(new CallStackDepth(callStack, depth + 1)), entry.getId(), element.getName(), parentEntry.getStartTime(), parentEntry.getEndTime(),
                         FlameChartEntryModel.EntryType.FUNCTION, depth + 1, hostThread);
                 builder.add(flameChartEntry);
                 if (depth == 0 && callStack.hasKernelStatuses()) {
-                    long start = parentEntry.getStartTime();
-                    long end = parentEntry.getEndTime();
-                    if (hostThread != null) {
-                        IHostModel model = ModelManager.getModelFor(hostThread.getHost());
-                        start = Math.min(start, model.getStartTime());
-                        end = Math.max(end, model.getEndTime());
-                    }
-                    builder.add(new FlameChartEntryModel(getKernelEntryId(flameChartEntry.getId()), entry.getId(), Collections.singletonList(String.valueOf(Messages.FlameChartDataProvider_KernelStatusTitle)), start, end,
-                            FlameChartEntryModel.EntryType.KERNEL, -1, hostThread));
+                    builder.add(new FlameChartEntryModel(getKernelEntryId(flameChartEntry.getId()), entry.getId(), String.valueOf(Messages.FlameChartDataProvider_KernelStatusTitle),
+                            parentEntry.getStartTime(), parentEntry.getEndTime(), FlameChartEntryModel.EntryType.KERNEL, -1, hostThread));
                 }
             }
+            if (!element.isLeaf()) {
+                for (ICallStackElement child : element.getChildrenElements()) {
+                    processCallStackElement(child, builder, entry);
+                }
+            }
+            return;
         }
-
         // Intermediate element, create entry and process children
-        FlameChartEntryModel entry = new FlameChartEntryModel(elementId, parentEntry.getId(), Collections.singletonList(element.getName()), parentEntry.getStartTime(), parentEntry.getEndTime(), FlameChartEntryModel.EntryType.LEVEL);
+        FlameChartEntryModel entry = new FlameChartEntryModel(elementId, parentEntry.getId(), element.getName(), parentEntry.getStartTime(), parentEntry.getEndTime(), FlameChartEntryModel.EntryType.LEVEL);
         builder.add(entry);
         for (ICallStackElement child : element.getChildrenElements()) {
             processCallStackElement(child, builder, entry);
diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartEntryModel.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartEntryModel.java
index b6da4cb..2183da6 100644
--- a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartEntryModel.java
+++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartEntryModel.java
@@ -11,8 +11,6 @@
 
 package org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented;
 
-import java.util.Collections;
-import java.util.List;
 import java.util.Objects;
 
 import org.eclipse.jdt.annotation.Nullable;
@@ -148,7 +146,7 @@
          * @return the relevant {@link FlameChartEntryModel}
          */
         public FlameChartEntryModel build() {
-            return new FlameChartEntryModel(fId, fParentId, Collections.singletonList(fName), fStartTime, fEndTime, fEntryType, fDepth, fHostThread);
+            return new FlameChartEntryModel(fId, fParentId, fName, fStartTime, fEndTime, fEntryType, fDepth, fHostThread);
         }
 
         /**
@@ -190,7 +188,7 @@
      * @param entryType
      *            The type of this entry
      */
-    public FlameChartEntryModel(long id, long parentId, List<String> name, long startTime, long endTime, EntryType entryType) {
+    public FlameChartEntryModel(long id, long parentId, String name, long startTime, long endTime, EntryType entryType) {
         super(id, parentId, name, startTime, endTime);
         fEntryType = entryType;
         fDepth = -1;
@@ -217,7 +215,7 @@
      *            The entry's unique hostThread or <code>null</code> if host
      *            thread not available or variable
      */
-    public FlameChartEntryModel(long elementId, long parentId, List<String> name, long startTime, long endTime, EntryType entryType, int depth, @Nullable HostThread hostThread) {
+    public FlameChartEntryModel(long elementId, long parentId, String name, long startTime, long endTime, EntryType entryType, int depth, @Nullable HostThread hostThread) {
         super(elementId, parentId, name, startTime, endTime);
         fEntryType = entryType;
         fDepth = depth;
diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/InstrumentedCallStackAnalysis.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/InstrumentedCallStackAnalysis.java
index 77e7dc6..d28d903 100644
--- a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/InstrumentedCallStackAnalysis.java
+++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/InstrumentedCallStackAnalysis.java
@@ -14,7 +14,9 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.ListenerList;
@@ -62,14 +64,10 @@
     /** CallStack stack-attribute */
     public static final String CALL_STACK = "CallStack"; //$NON-NLS-1$
 
-    private static final String[] DEFAULT_PROCESSES_PATTERN = new String[] { CallStackStateProvider.PROCESSES, "*" }; //$NON-NLS-1$
-    private static final String[] DEFAULT_THREADS_PATTERN = new String[] { "*" }; //$NON-NLS-1$
-
-    private static final List<String[]> PATTERNS = ImmutableList.of(DEFAULT_PROCESSES_PATTERN, DEFAULT_THREADS_PATTERN);
-
     private @Nullable CallStackSeries fCallStacks;
 
     private final CallGraphAnalysis fCallGraph;
+    private boolean fIsCallStackSeriesFinalized = false;
 
     /**
      * Listeners
@@ -108,15 +106,29 @@
     @Override
     public synchronized @Nullable CallStackSeries getCallStackSeries() {
         CallStackSeries callstacks = fCallStacks;
+        ITmfStateSystem ss = getStateSystem();
+        ITmfTrace trace = getTrace();
+        if (ss == null || trace == null) {
+            return null;
+        }
+        // Check before getting the CallStackSeries if the state system is
+        // in its final state
+        boolean isStateSystemBuilt = ss.waitUntilBuilt(0);
         if (callstacks == null) {
-            ITmfStateSystem ss = getStateSystem();
-            ITmfTrace trace = getTrace();
-            if (ss == null || trace == null) {
+            List<String[]> patterns = getPatterns();
+            if (patterns.isEmpty()) {
                 return null;
             }
-            callstacks = new CallStackSeries(ss, getPatterns(), 0, "", getCallStackHostResolver(trace), getCallStackTidResolver()); //$NON-NLS-1$
+            callstacks = new CallStackSeries(ss, patterns, 0, "", getCallStackHostResolver(trace), getCallStackTidResolver()); //$NON-NLS-1$
             fCallStacks = callstacks;
+        } else if (!fIsCallStackSeriesFinalized) {
+            callstacks.updateRootGroup(getPatterns(), 0);
         }
+        // This is the last update after the state system is completed
+        if (isStateSystemBuilt) {
+            fIsCallStackSeriesFinalized = true;
+        }
+
         return callstacks;
     }
 
@@ -184,7 +196,76 @@
      * @return The patterns for the different levels in the state system
      */
     protected List<String[]> getPatterns() {
-        return PATTERNS;
+        ITmfStateSystem ss = getStateSystem();
+        if (ss == null) {
+            return Collections.emptyList();
+        }
+        List<String> patterns = new ArrayList<>();
+        for (int quark : ss.getQuarks("*")) { //$NON-NLS-1$
+            patterns.addAll(findCallStackPatterns(ss, quark));
+        }
+        if (patterns.size() > 1) {
+            String[] firstElement = new String[] { patterns.get(0), patterns.get(1) };
+            patterns.remove(1);
+            patterns.remove(0);
+            List<String[]> groupDescriptorPatterns = new ArrayList<>();
+            groupDescriptorPatterns.add(firstElement);
+            groupDescriptorPatterns.addAll(patterns.stream().map(e -> new String @NonNull [] { e })
+                    .collect(Collectors.toList()));
+            return groupDescriptorPatterns;
+        }
+        return Collections.emptyList();
+    }
+
+    private List<String> findCallStackPatterns(ITmfStateSystem ss, int quark) {
+        LinkedList<String> patterns = new LinkedList<>();
+        int nCallStackChild = 0;
+        if (ss.getNbAttributes() == 0) {
+            return patterns;
+        }
+        List<Integer> subQuarks = ss.getSubAttributes(quark, false);
+        boolean isCallStack = false;
+        List<String> subPatterns = new LinkedList<>();
+        for (Integer subQuark : subQuarks) {
+            if (ss.getAttributeName(subQuark).equals(CALL_STACK)) {
+                isCallStack = true;
+            } else {
+                @NonNull
+                List<String> neighbourSubPatterns = subPatterns;
+                subPatterns = findCallStackPatterns(ss, subQuark);
+                if (!subPatterns.isEmpty()) {
+                    nCallStackChild += 1;
+                    mergeSubPatterns(subPatterns, neighbourSubPatterns);
+                } else {
+                    subPatterns = neighbourSubPatterns;
+                }
+            }
+        }
+        if (!subPatterns.isEmpty()) {
+            patterns.addAll(subPatterns);
+        }
+        if (nCallStackChild > 0 || isCallStack) {
+            patterns.push(ss.getAttributeName(quark));
+        }
+        return patterns;
+    }
+
+    private static void mergeSubPatterns(List<String> pattern1, List<String> pattern2) {
+        if (!pattern2.isEmpty()) {
+            // if subpatterns not empty then replace by a star everything
+            // different
+            for (int i = 0; i < Integer.max(pattern1.size(), pattern2.size()); i++) {
+                if (i > Integer.min(pattern1.size() - 1, pattern2.size() - 1)) {
+                    if (i > pattern1.size() - 1) {
+                        pattern1.add(pattern2.get(i));
+                    }
+                    continue;
+                }
+                if (!pattern1.get(i).equals(pattern2.get(i))) {
+                    pattern1.set(i, "*"); //$NON-NLS-1$
+                }
+            }
+        }
     }
 
     @Override
diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/InstrumentedCallStackElement.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/InstrumentedCallStackElement.java
index cad050b..fb8a6c3 100644
--- a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/InstrumentedCallStackElement.java
+++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/InstrumentedCallStackElement.java
@@ -261,9 +261,6 @@
      * @return The list of quarks containing the data
      */
     public List<Integer> getStackQuarks() {
-        if (!isLeaf()) {
-            throw new NoSuchElementException();
-        }
         int stackQuark = getStateSystem().optQuarkRelative(getQuark(), InstrumentedCallStackAnalysis.CALL_STACK);
         if (stackQuark == ITmfStateSystem.INVALID_ATTRIBUTE) {
             // No CallStack element underneath, assume a flat chart: the current
@@ -277,4 +274,10 @@
     public InstrumentedCallStackElement copyElement() {
         return new InstrumentedCallStackElement(fHostResolver, fStateSystem, fQuark, super.getGroup(), null, fThreadIdResolver, null);
     }
+
+    @Override
+    public boolean isCallStack() {
+        int stackQuark = getStateSystem().optQuarkRelative(getQuark(), InstrumentedCallStackAnalysis.CALL_STACK);
+        return stackQuark != ITmfStateSystem.INVALID_ATTRIBUTE;
+    }
 }