| /******************************************************************************* |
| * 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.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.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.tracecompass.incubator.analysis.core.concepts.AggregatedCallSite; |
| import org.eclipse.tracecompass.incubator.callstack.core.base.ICallStackElement; |
| import org.eclipse.tracecompass.incubator.callstack.core.callgraph.CallGraph; |
| import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.callgraph.AggregatedThreadStatus; |
| 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.TimeEvent; |
| 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<TimeGraphEntry> fFlameGraphEntries = new ArrayList<>(); |
| |
| private SortOption fSortOption = SortOption.BY_NAME; |
| private Comparator<TimeGraphEntry> fThreadComparator = new ThreadNameComparator(); |
| |
| private final Map<ICallStackElement, TimeGraphEntry> fLevelEntries = new HashMap<>(); |
| |
| /** |
| * 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(AggregatedCallSite firstNode, List<FlamegraphDepthEntry> childrenEntries, Deque<Long> timestampStack) { |
| Long lastEnd = timestampStack.peek(); |
| if (lastEnd == null) { |
| return; |
| } |
| for (int i = 0; i <= firstNode.getMaxDepth() - 1; i++) { |
| if (i >= childrenEntries.size()) { |
| FlamegraphDepthEntry entry = new FlamegraphDepthEntry(String.valueOf(i), 0, firstNode.getWeight(), i, String.valueOf(i)); |
| childrenEntries.add(entry); |
| } |
| childrenEntries.get(i).updateEndTime(lastEnd + firstNode.getWeight()); |
| } |
| 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, 1); |
| timestampStack.pop(); |
| } |
| |
| private static void setExtraData(AggregatedCallSite firstNode, List<TimeGraphEntry> extraEntries, Deque<Long> timestampStack) { |
| 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()) { |
| TimeGraphEntry entry = new TimeGraphEntry(Messages.FlameGraphContentProvider_EntryName, 0, 0); |
| extraEntries.add(entry); |
| } |
| TimeGraphEntry entry = extraEntries.get(0); |
| |
| // TODO: loop in the extra data and add the events |
| while (extraChildrenSites.hasNext()) { |
| AggregatedCallSite next = extraChildrenSites.next(); |
| if (next instanceof AggregatedThreadStatus) { |
| entry.addEvent(new TimeEvent(entry, lastEnd, next.getWeight(), ((AggregatedThreadStatus) next).getProcessStatus().getStateValue().unboxInt())); |
| } else { |
| entry.addEvent(new FlamegraphEvent(entry, lastEnd, next)); |
| } |
| lastEnd += next.getWeight(); |
| entry.updateEndTime(lastEnd); |
| } |
| } |
| |
| /** |
| * 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(AggregatedCallSite node, List<FlamegraphDepthEntry> childrenEntries, Deque<Long> timestampStack, int depth) { |
| node.getCallees().stream() |
| .sorted(Comparator.comparingLong(AggregatedCallSite::getWeight)) |
| .forEach(child -> { |
| addEvent(child, childrenEntries, timestampStack, depth + 1); |
| }); |
| node.getCallees().stream().forEach(child -> { |
| timestampStack.pop(); |
| }); |
| |
| Long lastEnd = timestampStack.peek(); |
| if (lastEnd == null) { |
| return; |
| } |
| FlamegraphDepthEntry entry = checkNotNull(childrenEntries.get(depth - 1)); |
| // Create the event corresponding to the function using the caller's |
| // timestamp |
| entry.addEvent(new FlamegraphEvent(entry, lastEnd, node)); |
| timestampStack.push(lastEnd + node.getWeight()); |
| } |
| |
| @Override |
| public boolean hasChildren(@Nullable Object element) { |
| return !fFlameGraphEntries.isEmpty(); |
| } |
| |
| @Override |
| public ITimeGraphEntry[] getElements(@Nullable Object inputElement) { |
| fFlameGraphEntries.clear(); |
| fLevelEntries.clear(); |
| // Get the root of each thread |
| if (inputElement instanceof Collection<?>) { |
| Collection<?> callgraphProviders = (Collection<?>) inputElement; |
| for (Object object : callgraphProviders) { |
| if (object instanceof CallGraph) { |
| buildCallGraph((CallGraph) object); |
| } |
| } |
| } else { |
| return new ITimeGraphEntry[0]; |
| } |
| |
| // Sort the threads |
| fFlameGraphEntries.sort(fThreadComparator); |
| return fFlameGraphEntries.toArray(new ITimeGraphEntry[fFlameGraphEntries.size()]); |
| } |
| |
| private void buildCallGraph(CallGraph object) { |
| Collection<ICallStackElement> elements = object.getElements(); |
| for (ICallStackElement element : elements) { |
| buildChildrenEntries(element, object, null); |
| } |
| } |
| |
| /** |
| * Build the entry list for one thread |
| * |
| * @param element |
| * The node of the aggregation tree |
| */ |
| private void buildChildrenEntries(ICallStackElement element, CallGraph object, @Nullable TimeGraphEntry parent) { |
| // Add the entry |
| TimeGraphEntry currentEntry = new TimeGraphEntry(element.getName(), 0L, 0L); |
| if (parent != null) { |
| parent.addChild(currentEntry); |
| } else { |
| fFlameGraphEntries.add(currentEntry); |
| } |
| |
| // Create the children entries |
| for (ICallStackElement child : element.getChildrenElements()) { |
| buildChildrenEntries(child, object, currentEntry); |
| } |
| |
| // Create the callsites entries |
| if (!(element.isLeaf())) { |
| return; |
| } |
| |
| List<FlamegraphDepthEntry> childrenEntries = new ArrayList<>(); |
| List<TimeGraphEntry> extraEntries = new ArrayList<>(); |
| Deque<Long> timestampStack = new ArrayDeque<>(); |
| timestampStack.push(0L); |
| |
| // Sort children by duration |
| object.getCallingContextTree(element).stream() |
| .sorted(Comparator.comparingLong(AggregatedCallSite::getWeight)) |
| .forEach(rootFunction -> { |
| setData(rootFunction, childrenEntries, timestampStack); |
| setExtraData(rootFunction, extraEntries, timestampStack); |
| long currentThreadDuration = timestampStack.pop() + rootFunction.getWeight(); |
| timestampStack.push(currentThreadDuration); |
| }); |
| childrenEntries.forEach(child -> { |
| currentEntry.addChild(child); |
| }); |
| extraEntries.forEach(child -> { |
| currentEntry.addChild(child); |
| }); |
| currentEntry.updateEndTime(timestampStack.pop()); |
| return; |
| |
| } |
| |
| @Override |
| public ITimeGraphEntry[] getChildren(@Nullable Object parentElement) { |
| return fFlameGraphEntries.toArray(new TimeGraphEntry[fFlameGraphEntries.size()]); |
| } |
| |
| @Override |
| public @Nullable ITimeGraphEntry getParent(@Nullable Object element) { |
| // Do nothing |
| return null; |
| } |
| |
| @Override |
| public void dispose() { |
| // Do nothing |
| } |
| |
| @Override |
| public void inputChanged(@Nullable Viewer viewer, @Nullable Object oldInput, @Nullable 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; |
| } |
| } |
| } |