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; + } }