blob: f6f431545cec8d51b61a578f95a22df031343e04 [file] [log] [blame]
/*******************************************************************************
* 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);
}
}