/******************************************************************************* * 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.internal.analysis.timing.ui.flamegraph; import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Deque; import java.util.List; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jface.viewers.Viewer; import org.eclipse.tracecompass.internal.analysis.timing.core.callgraph.AggregatedCalledFunction; import org.eclipse.tracecompass.internal.analysis.timing.core.callgraph.ThreadNode; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphContentProvider; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry; /** * Content provider for the flame graph view * * @author Sonia Farrah * */ public class FlameGraphContentProvider implements ITimeGraphContentProvider { private final List<FlamegraphDepthEntry> fFlameGraphEntries = new ArrayList<>(); private SortOption fSortOption = SortOption.BY_NAME; private @NonNull Comparator<FlamegraphDepthEntry> fThreadComparator = new ThreadNameComparator(); /** * Parse the aggregated tree created by the callGraphAnalysis and creates * the event list (functions) for each entry (depth) * * @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 void setData(AggregatedCalledFunction firstNode, List<FlamegraphDepthEntry> childrenEntries, Deque<Long> timestampStack) { long lastEnd = timestampStack.peek(); for (int i = 0; i < firstNode.getMaxDepth(); i++) { if (i >= childrenEntries.size()) { FlamegraphDepthEntry entry = new FlamegraphDepthEntry(String.valueOf(i), 0, firstNode.getDuration(), i, i); childrenEntries.add(entry); } childrenEntries.get(i).updateEndTime(lastEnd + firstNode.getDuration()); } FlamegraphDepthEntry firstEntry = checkNotNull(childrenEntries.get(0)); firstEntry.addEvent(new FlamegraphEvent(firstEntry, lastEnd, firstNode)); // Build the event list for next entries (next depth) addEvent(firstNode, childrenEntries, timestampStack); timestampStack.pop(); } /** * Build the events list for an entry (depth), then creates recursively the * events for the next entries. This parses the aggregation tree starting * from the bottom. This uses a stack to save the timestamp for each * function. Once we save a function's timestamp we'll use it to create the * callees events. * * @param node * The 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 void addEvent(AggregatedCalledFunction node, List<FlamegraphDepthEntry> childrenEntries, Deque<Long> timestampStack) { if (node.hasChildren()) { node.getChildren().stream() .sorted(Comparator.comparingLong(AggregatedCalledFunction::getDuration)) .forEach(child -> { addEvent(child, childrenEntries, timestampStack); }); node.getChildren().stream().forEach(child -> { timestampStack.pop(); }); } FlamegraphDepthEntry entry = checkNotNull(childrenEntries.get(node.getDepth())); // Create the event corresponding to the function using the caller's // timestamp entry.addEvent(new FlamegraphEvent(entry, timestampStack.peek(), node)); timestampStack.push(timestampStack.peek() + node.getDuration()); } @Override public boolean hasChildren(Object element) { return !fFlameGraphEntries.isEmpty(); } @Override public ITimeGraphEntry[] getElements(Object inputElement) { fFlameGraphEntries.clear(); // Get the root of each thread if (inputElement instanceof Collection<?>) { Collection<?> threadNodes = (Collection<?>) inputElement; for (Object object : threadNodes) { if (object instanceof ThreadNode) { buildChildrenEntries((ThreadNode) object); } } } else { return new ITimeGraphEntry[0]; } // Sort the threads fFlameGraphEntries.sort(fThreadComparator); return fFlameGraphEntries.toArray(new ITimeGraphEntry[fFlameGraphEntries.size()]); } /** * Build the entry list for one thread * * @param threadNode * The node of the aggregation tree */ private void buildChildrenEntries(ThreadNode threadNode) { FlamegraphDepthEntry threadEntry = new FlamegraphDepthEntry("", 0, 0, fFlameGraphEntries.size(), threadNode.getId()); //$NON-NLS-1$ List<FlamegraphDepthEntry> childrenEntries = new ArrayList<>(); Deque<Long> timestampStack = new ArrayDeque<>(); timestampStack.push(0L); // Sort children by duration threadNode.getChildren().stream() .sorted(Comparator.comparingLong(AggregatedCalledFunction::getDuration)) .forEach(rootFunction -> { setData(rootFunction, childrenEntries, timestampStack); long currentThreadDuration = timestampStack.pop() + rootFunction.getDuration(); timestampStack.push(currentThreadDuration); }); childrenEntries.forEach(child -> { if (child != null) { threadEntry.addChild(child); } }); threadEntry.updateEndTime(timestampStack.pop()); threadEntry.setName(threadNode.getSymbol().toString()); fFlameGraphEntries.add(threadEntry); } @Override public ITimeGraphEntry[] getChildren(Object parentElement) { return fFlameGraphEntries.toArray(new TimeGraphEntry[fFlameGraphEntries.size()]); } @Override public ITimeGraphEntry getParent(Object element) { // Do nothing return null; } @Override public void dispose() { // Do nothing } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { // Do nothing } /** * Get the sort option * * @return the sort option. */ public SortOption getSortOption() { return fSortOption; } /** * Set the sort option for sorting the thread entries * * @param sortOption * the sort option to set * */ public void setSortOption(SortOption sortOption) { fSortOption = sortOption; switch (sortOption) { case BY_NAME: fThreadComparator = new ThreadNameComparator(); break; case BY_NAME_REV: fThreadComparator = checkNotNull(new ThreadNameComparator().reversed()); break; case BY_ID: fThreadComparator = new ThreadIdComparator(); break; case BY_ID_REV: fThreadComparator = checkNotNull(new ThreadIdComparator().reversed()); break; default: break; } } }