blob: 870252997c9083e1f997991fe5c0fd37c00bd188 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 É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.instrumented.provider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
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.os.linux.core.model.HostThread;
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.callstack.core.base.EdgeStateValue;
import org.eclipse.tracecompass.incubator.callstack.core.base.ICallStackElement;
import org.eclipse.tracecompass.incubator.callstack.core.flamechart.CallStack;
import org.eclipse.tracecompass.incubator.callstack.core.instrumented.CallStackDepth;
import org.eclipse.tracecompass.incubator.callstack.core.instrumented.ICalledFunction;
import org.eclipse.tracecompass.incubator.callstack.core.instrumented.IFlameChartProvider;
import org.eclipse.tracecompass.incubator.callstack.core.instrumented.statesystem.CallStackSeries;
import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.InstrumentedCallStackElement;
import org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.FlameChartEntryModel.EntryType;
import org.eclipse.tracecompass.incubator.internal.callstack.core.palette.FlameDefaultPalette;
import org.eclipse.tracecompass.incubator.internal.callstack.core.palette.FlameWithKernelPalette;
import org.eclipse.tracecompass.internal.analysis.os.linux.core.threadstatus.ThreadEntryModel;
import org.eclipse.tracecompass.internal.analysis.os.linux.core.threadstatus.ThreadStatusDataProvider;
import org.eclipse.tracecompass.internal.tmf.core.model.AbstractTmfTraceDataProvider;
import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
import org.eclipse.tracecompass.segmentstore.core.ISegment;
import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderManager;
import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils;
import org.eclipse.tracecompass.tmf.core.model.CommonStatusMessage;
import org.eclipse.tracecompass.tmf.core.model.IOutputStyleProvider;
import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle;
import org.eclipse.tracecompass.tmf.core.model.OutputStyleModel;
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.ITimeGraphStateFilter;
import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphArrow;
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.ITmfResponse.Status;
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.symbols.SymbolProviderUtils;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.core.util.Pair;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
/**
* This class provides the data from an instrumented callstack analysis, in the
* form of a flamechart, ie the groups are returned hierarchically and leaf
* groups return their callstacks.
*
* @author Geneviève Bastien
*/
@SuppressWarnings("restriction")
public class FlameChartDataProvider extends AbstractTmfTraceDataProvider implements ITimeGraphDataProvider<FlameChartEntryModel>, IOutputStyleProvider {
/**
* Provider ID.
*/
public static final String ID = "org.eclipse.tracecompass.incubator.internal.callstack.core.instrumented.provider.flamechart"; //$NON-NLS-1$
private static final AtomicLong ENTRY_ID = new AtomicLong();
/**
* Logger for Abstract Tree Data Providers.
*/
private static final Logger LOGGER = TraceCompassLog.getLogger(FlameChartDataProvider.class);
private final Map<Long, FlameChartEntryModel> fEntries = new HashMap<>();
// Key is the row ID that requires linked data (for instance a kernel row) and
// value is the row being linked to (the one from the callstack)
private final BiMap<Long, Long> fLinkedEntries = HashBiMap.create();
private final Collection<ISymbolProvider> fProviders = new ArrayList<>();
private final BiMap<Long, CallStackDepth> fIdToCallstack = HashBiMap.create();
private final BiMap<Long, ICallStackElement> fIdToElement = HashBiMap.create();
private final long fTraceId = ENTRY_ID.getAndIncrement();
/** Cache for entry metadata */
private final Map<Long, @NonNull Multimap<@NonNull String, @NonNull Object>> fEntryMetadata = new HashMap<>();
private static class TidInformation {
private final HostThread fTid;
private final long fStart;
private final long fEnd;
/* The ID of the entry in this data provider where to put the thread information */
private final Long fLinked;
public TidInformation(HostThread hostThread, long start, long end, Long linked) {
fTid = hostThread;
fStart = start;
fEnd = end;
fLinked = linked;
}
public boolean intersects(ITimeGraphState state) {
return !(state.getStartTime() > fEnd || (state.getStartTime() + state.getDuration()) < fStart);
}
public boolean precedes(ITimeGraphState state) {
return (state.getStartTime() + state.getDuration() < fEnd);
}
public ITimeGraphState sanitize(ITimeGraphState state) {
if (state.getStartTime() < fStart || state.getStartTime() + state.getDuration() > fEnd) {
long start = Math.max(state.getStartTime(), fStart);
long end = Math.min(state.getStartTime() + state.getDuration(), fEnd);
return new TimeGraphState(start, end - start, state.getLabel(), state.getStyle());
}
return state;
}
}
private static class ThreadData {
private final ThreadStatusDataProvider fThreadDataProvider;
private final List<ThreadEntryModel> fThreadTree;
private final Status fStatus;
public ThreadData(ThreadStatusDataProvider dataProvider, List<ThreadEntryModel> threadTree, Status status) {
fThreadDataProvider = dataProvider;
fThreadTree = threadTree;
fStatus = status;
}
public @Nullable Map<String, String> fetchTooltip(int threadId, long time, @Nullable IProgressMonitor monitor) {
for (ThreadEntryModel entry : fThreadTree) {
if (entry.getThreadId() == threadId && entry.getStartTime() <= time && entry.getEndTime() >= time) {
TmfModelResponse<Map<String, String>> tooltip = fThreadDataProvider.fetchTooltip(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(Collections.singletonList(time), Collections.singleton(entry.getId()))), monitor);
return tooltip.getModel();
}
}
return null;
}
}
private final LoadingCache<Pair<Integer, ICalledFunction>, @Nullable String> fTimeEventNames = Objects.requireNonNull(CacheBuilder.newBuilder()
.maximumSize(1000)
.build(new CacheLoader<Pair<Integer, ICalledFunction>, @Nullable String>() {
@Override
public @Nullable String load(Pair<Integer, ICalledFunction> pidInterval) {
Integer pid = pidInterval.getFirst();
ICalledFunction interval = pidInterval.getSecond();
Object nameValue = interval.getSymbol();
Long address = null;
String name = null;
if (nameValue instanceof String) {
name = (String) nameValue;
try {
address = Long.parseLong(name, 16);
} catch (NumberFormatException e) {
// leave name as null
}
} else if (nameValue instanceof Integer) {
Integer intValue = (Integer) nameValue;
name = "0x" + Integer.toUnsignedString(intValue, 16); //$NON-NLS-1$
address = intValue.longValue();
} else if (nameValue instanceof Long) {
address = (long) nameValue;
name = "0x" + Long.toUnsignedString(address, 16); //$NON-NLS-1$
}
if (address != null) {
name = SymbolProviderUtils.getSymbolText(fProviders, pid, interval.getStart(), address);
}
return name;
}
}));
private final IFlameChartProvider fFcProvider;
private final String fAnalysisId;
private final FlameChartArrowProvider fArrowProvider;
private @Nullable TmfModelResponse<TmfTreeModel<FlameChartEntryModel>> fCached;
private @Nullable ThreadData fThreadData = null;
/**
* Constructor
*
* @param trace
* The trace for which this data provider applies
* @param module
* The flame chart provider encapsulated by this provider
* @param secondaryId
* The ID of the flame chart provider
*/
public FlameChartDataProvider(ITmfTrace trace, IFlameChartProvider module, String secondaryId) {
super(trace);
fFcProvider = module;
fAnalysisId = secondaryId;
fArrowProvider = new FlameChartArrowProvider(trace);
resetFunctionNames(new NullProgressMonitor());
}
@Override
public TmfModelResponse<List<ITimeGraphArrow>> fetchArrows(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
List<ITmfStateInterval> arrows = fArrowProvider.fetchArrows(fetchParameters, monitor);
List<ITimeGraphArrow> tgArrows = new ArrayList<>();
// First, get the distinct callstacks
Set<CallStack> callstacks = fIdToCallstack.values().stream()
.map(CallStackDepth::getCallStack)
.distinct()
.collect(Collectors.toSet());
// Find the source and destination entry for each arrow
for (ITmfStateInterval interval : arrows) {
if (monitor != null && monitor.isCanceled()) {
return new TmfModelResponse<>(null, Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED);
}
EdgeStateValue edge = (EdgeStateValue) interval.getValue();
if (edge == null) {
/*
* by contract all the intervals should have EdgeStateValues but
* need to check to avoid NPE
*/
continue;
}
Long src = findEntry(callstacks, edge.getSource(), interval.getStartTime());
Long dst = findEntry(callstacks, edge.getDestination(), interval.getEndTime() + 1);
if (src != null && dst != null) {
long duration = interval.getEndTime() - interval.getStartTime() + 1;
tgArrows.add(new TimeGraphArrow(src, dst, interval.getStartTime(), duration, edge.getId()));
}
}
return new TmfModelResponse<>(tgArrows, Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
private @Nullable Long findEntry(Set<CallStack> callstacks, HostThread hostThread, long ts) {
for (CallStack callstack : callstacks) {
// Get the host thread running on the callstack and compare with desired
HostThread csHt = callstack.getHostThread(ts);
if (csHt == null || !csHt.equals(hostThread)) {
continue;
}
// We found the callstack, find the right depth and its entry id
int currentDepth = callstack.getCurrentDepth(ts);
for (Entry<Long, CallStackDepth> csdEntry : fIdToCallstack.entrySet()) {
CallStackDepth csd = csdEntry.getValue();
// For callstack, compare objects, not content
if (csd.getDepth() == currentDepth && csd.getCallStack() == callstack) {
return csdEntry.getKey();
}
}
}
return null;
}
@Override
public TmfModelResponse<Map<String, String>> fetchTooltip(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameChartDataProvider#fetchTooltip") //$NON-NLS-1$
.setCategory(getClass().getSimpleName()).build()) {
List<Long> times = DataProviderParameterUtils.extractTimeRequested(fetchParameters);
if (times == null || times.isEmpty()) {
// No time specified
return new TmfModelResponse<>(null, Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
Map<Long, FlameChartEntryModel> entries = getSelectedEntries(fetchParameters);
if (entries.size() != 1) {
// Not the expected size of tooltip, just return empty
return new TmfModelResponse<>(null, Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
Entry<@NonNull Long, @NonNull FlameChartEntryModel> entry = entries.entrySet().iterator().next();
Map<String, String> tooltip = getTooltip(entry.getKey(), entry.getValue(), times.get(0), monitor);
return new TmfModelResponse<>(tooltip, Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
}
private @Nullable Map<String, String> getTooltip(Long entryId, FlameChartEntryModel entryModel, Long time, @Nullable IProgressMonitor monitor) {
switch (entryModel.getEntryType()) {
case FUNCTION: {
CallStackDepth selectedDepth = fIdToCallstack.get(entryId);
if (selectedDepth == null) {
return null;
}
Multimap<CallStackDepth, ISegment> csFunctions = fFcProvider.queryCallStacks(Collections.singleton(selectedDepth), Collections.singleton(time));
Collection<ISegment> functions = csFunctions.get(selectedDepth);
if (functions.isEmpty()) {
return null;
}
ISegment next = functions.iterator().next();
if (!(next instanceof ICalledFunction)) {
return null;
}
ICalledFunction currentFct = (ICalledFunction) next;
Map<String, String> tooltips = new HashMap<>();
int threadId = currentFct.getThreadId();
if (threadId > 0) {
tooltips.put(String.valueOf(Messages.FlameChartDataProvider_ThreadId), String.valueOf(threadId));
}
Object symbol = currentFct.getSymbol();
tooltips.put(String.valueOf(Messages.FlameChartDataProvider_Symbol), symbol instanceof Long ? "0x" + Long.toHexString((Long) symbol) : String.valueOf(symbol)); //$NON-NLS-1$
// TODO: Add symbol origin (library, language, etc) when better supported
return tooltips;
}
case KERNEL:
// Get the tooltip from the the ThreadStatusDataProvider
// First get the linked function to know which TID to retrieve
Long csId = fLinkedEntries.get(entryId);
if (csId == null) {
return null;
}
CallStackDepth selectedDepth = fIdToCallstack.get(csId);
if (selectedDepth == null) {
return null;
}
int threadId = selectedDepth.getCallStack().getThreadId(time);
ThreadData threadData = fThreadData;
if (threadData == null) {
return null;
}
return threadData.fetchTooltip(threadId, time, monitor);
case LEVEL:
// Fall-through
case TRACE:
// Fall-through
default:
return null;
}
}
@Override
public String getId() {
return ID + ':' + fAnalysisId;
}
// Get an entry for a quark
private long getEntryId(CallStackDepth stack) {
return fIdToCallstack.inverse().computeIfAbsent(stack, q -> ENTRY_ID.getAndIncrement());
}
private long getEntryId(ICallStackElement instrumentedCallStackElement) {
return fIdToElement.inverse().computeIfAbsent(instrumentedCallStackElement, q -> ENTRY_ID.getAndIncrement());
}
// Get a new entry for a kernel entry ID
private long getKernelEntryId(long baseId) {
return fLinkedEntries.inverse().computeIfAbsent(baseId, id -> ENTRY_ID.getAndIncrement());
}
@Override
public TmfModelResponse<TmfTreeModel<FlameChartEntryModel>> fetchTree(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
if (fCached != null) {
return fCached;
}
try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameChartDataProvider#fetchTree") //$NON-NLS-1$
.setCategory(getClass().getSimpleName()).build()) {
IFlameChartProvider fcProvider = fFcProvider;
boolean complete = fcProvider.isComplete();
CallStackSeries callstack = fcProvider.getCallStackSeries();
if (callstack == null) {
return new TmfModelResponse<>(null, ITmfResponse.Status.FAILED, CommonStatusMessage.ANALYSIS_INITIALIZATION_FAILED);
}
long start = getTrace().getStartTime().getValue();
long end = getTrace().readEnd().getValue();
// Initialize the first element of the tree
ImmutableList.Builder<FlameChartEntryModel> builder = ImmutableList.builder();
FlameChartEntryModel traceEntry = new FlameChartEntryModel(fTraceId, -1, Collections.singletonList(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
boolean needsKernel = false;
for (ICallStackElement element : callstack.getRootElements()) {
if (monitor != null && monitor.isCanceled()) {
return new TmfModelResponse<>(null, ITmfResponse.Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED);
}
needsKernel |= processCallStackElement(element, builder, callStackRoot);
}
// Initialize the thread status data provider
if (needsKernel) {
prepareKernelData(monitor, start);
}
List<FlameChartEntryModel> tree = builder.build();
tree.forEach(entry -> fEntries.put(entry.getId(), entry));
for (FlameChartEntryModel model : tree) {
fEntryMetadata.put(model.getId(), model.getMetadata());
}
if (complete) {
TmfModelResponse<TmfTreeModel<FlameChartEntryModel>> response = new TmfModelResponse<>(new TmfTreeModel<>(Collections.emptyList(), tree),
ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
fCached = response;
return response;
}
return new TmfModelResponse<>(new TmfTreeModel<>(Collections.emptyList(), tree), ITmfResponse.Status.RUNNING, CommonStatusMessage.RUNNING);
}
}
private void prepareKernelData(@Nullable IProgressMonitor monitor, long start) {
ThreadData data = fThreadData;
if (data != null && data.fStatus.equals(Status.COMPLETED)) {
return;
}
// FIXME: Wouldn't work correctly if trace is an experiment as it would cover
// many hosts
Set<ITmfTrace> tracesForHost = TmfTraceManager.getInstance().getTracesForHost(getTrace().getHostId());
for (ITmfTrace trace : tracesForHost) {
ThreadStatusDataProvider dataProvider = DataProviderManager.getInstance().getDataProvider(trace, ThreadStatusDataProvider.ID, ThreadStatusDataProvider.class);
if (dataProvider != null) {
// Get the tree for the trace's current range
TmfModelResponse<TmfTreeModel<ThreadEntryModel>> threadTreeResp = dataProvider.fetchTree(FetchParametersUtils.timeQueryToMap(new TimeQueryFilter(start, Long.MAX_VALUE, 2)), monitor);
TmfTreeModel<ThreadEntryModel> threadTree = threadTreeResp.getModel();
if (threadTree != null) {
fThreadData = new ThreadData(dataProvider, threadTree.getEntries(), threadTreeResp.getStatus());
break;
}
}
}
}
private boolean processCallStackElement(ICallStackElement element, Builder<FlameChartEntryModel> builder, FlameChartEntryModel parentEntry) {
long elementId = getEntryId(element);
boolean needsKernel = false;
// Is this an intermediate or leaf element
if ((element instanceof InstrumentedCallStackElement) && element.isLeaf()) {
// 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);
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.EntryType.FUNCTION, depth + 1, hostThread);
builder.add(flameChartEntry);
if (depth == 0 && callStack.hasKernelStatuses()) {
needsKernel = true;
builder.add(new FlameChartEntryModel(getKernelEntryId(flameChartEntry.getId()), entry.getId(), Collections.singletonList(String.valueOf(Messages.FlameChartDataProvider_KernelStatusTitle)), parentEntry.getStartTime(), parentEntry.getEndTime(),
FlameChartEntryModel.EntryType.KERNEL, -1, hostThread));
}
}
return needsKernel;
}
// 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);
builder.add(entry);
for (ICallStackElement child : element.getChildrenElements()) {
needsKernel |= processCallStackElement(child, builder, entry);
}
return needsKernel;
}
// Get the selected entries with the quark
private BiMap<Long, FlameChartEntryModel> getSelectedEntries(Map<String, Object> fetchParameters) {
BiMap<Long, FlameChartEntryModel> selectedEntries = HashBiMap.create();
List<Long> ids = DataProviderParameterUtils.extractSelectedItems(fetchParameters);
if (ids == null) {
return selectedEntries;
}
for (Long selectedItem : ids) {
FlameChartEntryModel entryModel = fEntries.get(selectedItem);
if (entryModel != null) {
selectedEntries.put(selectedItem, entryModel);
}
}
return selectedEntries;
}
@Override
public TmfModelResponse<TimeGraphModel> fetchRowModel(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameChartDataProvider#fetchRowModel") //$NON-NLS-1$
.setCategory(getClass().getSimpleName()).build()) {
Map<Long, FlameChartEntryModel> entries = getSelectedEntries(fetchParameters);
List<Long> times = DataProviderParameterUtils.extractTimeRequested(fetchParameters);
if (times == null) {
// No time specified
return new TmfModelResponse<>(null, Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
if (entries.size() == 1 && times.size() == 2) {
// this is a request for a follow event.
Entry<@NonNull Long, @NonNull FlameChartEntryModel> entry = entries.entrySet().iterator().next();
if (times.get(0) == Long.MIN_VALUE) {
List<ITimeGraphRowModel> followEvents = getFollowEvent(entry, times.get(times.size() - 1), false);
TimeGraphModel model = followEvents == null ? null : new TimeGraphModel(followEvents);
return new TmfModelResponse<>(model, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
} else if (times.get(times.size() - 1) == Long.MAX_VALUE) {
List<ITimeGraphRowModel> followEvents = getFollowEvent(entry, times.get(0), true);
TimeGraphModel model = followEvents == null ? null : new TimeGraphModel(followEvents);
return new TmfModelResponse<>(model, ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
}
// For each kernel status entry, add the first row of the callstack
addRequiredCallstacks(entries);
SubMonitor subMonitor = SubMonitor.convert(monitor, "FlameChartDataProvider#fetchRowModel", 2); //$NON-NLS-1$
IFlameChartProvider fcProvider = fFcProvider;
boolean complete = fcProvider.isComplete();
Map<Long, List<ITimeGraphState>> csRows = getCallStackRows(fetchParameters, entries, subMonitor);
if (csRows == null) {
// getRowModel returns null if the query was cancelled.
return new TmfModelResponse<>(null, ITmfResponse.Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED);
}
List<ITimeGraphRowModel> collect = csRows.entrySet().stream().map(entry -> new TimeGraphRowModel(entry.getKey(), entry.getValue())).collect(Collectors.toList());
return new TmfModelResponse<>(new TimeGraphModel(collect), complete ? Status.COMPLETED : Status.RUNNING,
complete ? CommonStatusMessage.COMPLETED : CommonStatusMessage.RUNNING);
} catch (IndexOutOfBoundsException | TimeRangeException e) {
return new TmfModelResponse<>(null, Status.FAILED, String.valueOf(e.getMessage()));
}
}
private void addRequiredCallstacks(Map<Long, FlameChartEntryModel> entries) {
Map<Long, FlameChartEntryModel> toAdd = new HashMap<>();
for (Long id : entries.keySet()) {
Long csId = fLinkedEntries.get(id);
if (csId != null) {
FlameChartEntryModel entry = fEntries.get(csId);
if (entry != null) {
toAdd.put(csId, entry);
}
}
}
entries.putAll(toAdd);
}
private @Nullable Map<Long, List<ITimeGraphState>> getCallStackRows(Map<String, Object> fetchParameters, Map<Long, FlameChartEntryModel> entries, SubMonitor subMonitor)
throws IndexOutOfBoundsException, TimeRangeException {
// Get the data for the model entries that are of type function
Map<Long, List<ITimeGraphState>> rows = new HashMap<>();
List<TidInformation> tids = new ArrayList<>();
Map<Long, CallStackDepth> csEntries = new HashMap<>();
for (Entry<Long, @NonNull FlameChartEntryModel> entry : entries.entrySet()) {
CallStackDepth selectedDepth = fIdToCallstack.get(entry.getKey());
if (selectedDepth != null && entry.getValue().getEntryType().equals(EntryType.FUNCTION)) {
csEntries.put(entry.getKey(), selectedDepth);
}
}
List<Long> times = DataProviderParameterUtils.extractTimeRequested(fetchParameters);
Collections.sort(times);
Multimap<CallStackDepth, ISegment> csFunctions = fFcProvider.queryCallStacks(csEntries.values(), Objects.requireNonNull(times));
// 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));
}
for (Map.Entry<Long, CallStackDepth> entry : csEntries.entrySet()) {
if (subMonitor.isCanceled()) {
return null;
}
Collection<ISegment> states = csFunctions.get(entry.getValue());
Long key = Objects.requireNonNull(entry.getKey());
// Create the time graph states for this row
List<ITimeGraphState> eventList = new ArrayList<>(states.size());
states.forEach(state -> {
ITimeGraphState timeGraphState = createTimeGraphState(state);
applyFilterAndAddState(eventList, timeGraphState, key, predicates, subMonitor);
});
eventList.sort(Comparator.comparingLong(ITimeGraphState::getStartTime));
rows.put(entry.getKey(), eventList);
// See if any more row needs to be filled with these function's data
// TODO: Kernel might not be the only type of linked entries (for instance,
// locations of sampling data)
Long linked = fLinkedEntries.inverse().get(entry.getKey());
if (linked == null || !entries.containsKey(linked)) {
continue;
}
tids.addAll(getKernelTids(entry.getValue(), states, linked));
}
// Add an empty state to rows that do not have data
for (Long key : entries.keySet()) {
if (!rows.containsKey(key)) {
rows.put(key, Collections.emptyList());
}
}
if (!tids.isEmpty()) {
rows.putAll(getKernelStates(tids, times, predicates, subMonitor));
}
subMonitor.worked(1);
return rows;
}
private Map<Long, List<ITimeGraphState>> getKernelStates(List<TidInformation> tids, List<Long> times, Map<Integer, Predicate<Multimap<String, Object>>> predicates, SubMonitor monitor) {
// Get the thread statuses from the thread status provider
ThreadData threadData = fThreadData;
if (threadData == null) {
return Collections.emptyMap();
}
List<ThreadEntryModel> tree = threadData.fThreadTree;
// FIXME: A callstack analysis may be for an experiment that span many hosts,
// the thread data provider will be a composite and the models may be for
// different host IDs. But for now, suppose the callstack is a composite also
// and the trace filtered the right host.
BiMap<Long, Integer> threadModelIds = filterThreads(tree, tids);
SelectionTimeQueryFilter tidFilter = new SelectionTimeQueryFilter(times, threadModelIds.keySet());
TmfModelResponse<TimeGraphModel> rowModel = threadData.fThreadDataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(tidFilter), monitor);
TimeGraphModel rowModels = rowModel.getModel();
if (rowModel.getStatus().equals(Status.CANCELLED) || rowModel.getStatus().equals(Status.FAILED) || rowModels == null) {
return Collections.emptyMap();
}
return mapThreadStates(rowModels.getRows(), threadModelIds, tids, predicates, monitor);
}
private Map<Long, List<ITimeGraphState>> mapThreadStates(List<ITimeGraphRowModel> rowModels, BiMap<Long, Integer> threadModelIds, List<TidInformation> tids, Map<Integer, Predicate<Multimap<String, Object>>> predicates, SubMonitor monitor) {
ImmutableMap<Long, ITimeGraphRowModel> statusRows = Maps.uniqueIndex(rowModels, m -> m.getEntryID());
// Match the states of thread status to the requested tid lines
Long prevId = -1L;
List<ITimeGraphState> states = null;
Map<Long, List<ITimeGraphState>> kernelStatuses = new HashMap<>();
// The tid information data are ordered by id and times
for (TidInformation tidInfo : tids) {
// Get the ID of the linked callstack entry ID to get the filter data
Long linkedCsEntryId = fLinkedEntries.get(tidInfo.fLinked);
if (linkedCsEntryId == null) {
// fallback to the entry's own ID
linkedCsEntryId = tidInfo.fLinked;
}
Long tidEntryId = threadModelIds.inverse().get(tidInfo.fTid.getTid());
if (tidEntryId == null) {
continue;
}
ITimeGraphRowModel rowModel = statusRows.get(tidEntryId);
if (tidInfo.fLinked != prevId || states == null) {
if (states != null) {
kernelStatuses.put(prevId, states);
}
states = new ArrayList<>();
}
rowModel.getStates();
for (ITimeGraphState state : rowModel.getStates()) {
if (tidInfo.intersects(state)) {
ITimeGraphState timeGraphState = tidInfo.sanitize(state);
// Use the callstack entry's filter data
applyFilterAndAddState(states, timeGraphState, linkedCsEntryId, predicates, monitor);
}
if (!tidInfo.precedes(state)) {
break;
}
}
prevId = tidInfo.fLinked;
}
if (states != null) {
kernelStatuses.put(prevId, states);
}
return kernelStatuses;
}
private static BiMap<Long, Integer> filterThreads(List<ThreadEntryModel> model, List<TidInformation> tids) {
// Get the entry model IDs that match requested tids
BiMap<Long, Integer> tidEntries = HashBiMap.create();
Set<Integer> selectedTids = new HashSet<>();
for (TidInformation tidInfo : tids) {
selectedTids.add(tidInfo.fTid.getTid());
}
for (ThreadEntryModel entryModel : model) {
if (selectedTids.contains(entryModel.getThreadId())) {
try {
tidEntries.put(entryModel.getId(), entryModel.getThreadId());
} catch (IllegalArgumentException e) {
// FIXME: There may be many entries for one tid, don't rely on exception for
// real workflow. Works for now.
}
}
}
return tidEntries;
}
private static Collection<TidInformation> getKernelTids(CallStackDepth callStackDepth, Collection<ISegment> states, Long linked) {
List<TidInformation> tids = new ArrayList<>();
CallStack callStack = callStackDepth.getCallStack();
if (!callStack.isTidVariable()) {
// Find the time of the first function to know which timestamp to query
HostThread hostThread = callStack.getHostThread();
if (hostThread != null) {
tids.add(new TidInformation(hostThread, Long.MIN_VALUE, Long.MAX_VALUE, linked));
}
return tids;
}
// Get the thread IDs for all functions
for (ISegment state : states) {
if (!(state instanceof ICalledFunction)) {
continue;
}
ICalledFunction function = (ICalledFunction) state;
HostThread hostThread = callStack.getHostThread(function.getStart());
if (hostThread != null) {
tids.add(new TidInformation(hostThread, function.getStart(), function.getEnd(), linked));
}
}
return tids;
}
private ITimeGraphState createTimeGraphState(ISegment state) {
if (!(state instanceof ICalledFunction)) {
return new TimeGraphState(state.getStart(), state.getLength(), Integer.MIN_VALUE);
}
ICalledFunction function = (ICalledFunction) state;
Integer pid = function.getProcessId();
String name = String.valueOf(fTimeEventNames.getUnchecked(new Pair<>(pid, function)));
return new TimeGraphState(function.getStart(), function.getLength(), name, FlameDefaultPalette.getInstance().getStyleFor(state));
}
/**
* Invalidate the function names cache and load the symbol providers. This
* function should be used at the beginning of the provider, or whenever new
* symbol providers are added
*
* @param monitor
* A progress monitor to follow this operation
*/
public void resetFunctionNames(IProgressMonitor monitor) {
fTimeEventNames.invalidateAll();
synchronized (fProviders) {
Collection<@NonNull ISymbolProvider> symbolProviders = SymbolProviderManager.getInstance().getSymbolProviders(getTrace());
SubMonitor sub = SubMonitor.convert(monitor, "CallStackDataProvider#resetFunctionNames", symbolProviders.size()); //$NON-NLS-1$
fProviders.clear();
for (ISymbolProvider symbolProvider : symbolProviders) {
fProviders.add(symbolProvider);
symbolProvider.loadConfiguration(sub);
sub.worked(1);
}
}
}
/**
* Get the next or previous interval for a call stack entry ID, time and
* direction
*
* @param entry
* whose key is the ID and value is the quark for the entry whose
* next / previous state we are searching for
* @param time
* selection start time
* @param forward
* if going to next or previous
* @return the next / previous state encapsulated in a row if it exists, else
* null
*/
private @Nullable List<ITimeGraphRowModel> getFollowEvent(Entry<Long, FlameChartEntryModel> entry, long time, boolean forward) {
FlameChartEntryModel value = Objects.requireNonNull(entry.getValue());
switch (value.getEntryType()) {
case FUNCTION:
CallStackDepth selectedDepth = fIdToCallstack.get(entry.getKey());
if (selectedDepth == null) {
return null;
}
// Ask the callstack the depth at the current time
ITmfStateInterval nextDepth = selectedDepth.getCallStack().getNextDepth(time, forward);
if (nextDepth == null) {
return null;
}
Object depthVal = nextDepth.getValue();
int depth = (depthVal instanceof Number) ? ((Number) depthVal).intValue() : 0;
TimeGraphState state = new TimeGraphState(nextDepth.getStartTime(), nextDepth.getEndTime() - nextDepth.getStartTime(), depth);
TimeGraphRowModel row = new TimeGraphRowModel(entry.getKey(), Collections.singletonList(state));
return Collections.singletonList(row);
case KERNEL:
break;
case LEVEL:
// Fall-through
case TRACE:
// Fall-through
default:
return null;
}
return null;
}
@Override
public @NonNull Multimap<@NonNull String, @NonNull Object> getFilterData(long entryId, long time, @Nullable IProgressMonitor monitor) {
Multimap<@NonNull String, @NonNull Object> data = ITimeGraphStateFilter.mergeMultimaps(ITimeGraphDataProvider.super.getFilterData(entryId, time, monitor),
fEntryMetadata.getOrDefault(entryId, ImmutableMultimap.of()));
FlameChartEntryModel entryModel = fEntries.get(entryId);
if (entryModel == null) {
return data;
}
Map<String, String> tooltip = getTooltip(entryId, entryModel, time, monitor);
if (tooltip == null) {
return data;
}
for (Entry<String, String> entry : tooltip.entrySet()) {
data.put(entry.getKey(), entry.getValue());
}
return data;
}
@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);
}
@Override
public TmfModelResponse<OutputStyleModel> fetchStyle(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
// Use the palette with kernel styles, at worst, kernel styles won't be used
Map<String, OutputElementStyle> styles = FlameWithKernelPalette.getInstance().getStyles();
return new TmfModelResponse<>(new OutputStyleModel(styles), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);
}
}