diff options
Diffstat (limited to 'callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartDataProvider.java')
-rw-r--r-- | callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartDataProvider.java | 740 |
1 files changed, 740 insertions, 0 deletions
diff --git a/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartDataProvider.java b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartDataProvider.java new file mode 100644 index 000000000..885efab8e --- /dev/null +++ b/callstack/org.eclipse.tracecompass.incubator.callstack.core/src/org/eclipse/tracecompass/incubator/internal/callstack/core/instrumented/provider/FlameChartDataProvider.java @@ -0,0 +1,740 @@ +/******************************************************************************* + * 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.concurrent.locks.ReentrantReadWriteLock; +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.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.internal.analysis.os.linux.core.threadstatus.ThreadEntryModel; +import org.eclipse.tracecompass.internal.analysis.os.linux.core.threadstatus.ThreadStatusDataProvider; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.AbstractTmfTraceDataProvider; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.CommonStatusMessage; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.SelectionTimeQueryFilter; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.TimeQueryFilter; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.ITimeGraphArrow; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.ITimeGraphDataProvider; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.ITimeGraphRowModel; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.ITimeGraphState; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.TimeGraphRowModel; +import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.TimeGraphState; +import org.eclipse.tracecompass.internal.provisional.tmf.core.response.ITmfResponse; +import org.eclipse.tracecompass.internal.provisional.tmf.core.response.ITmfResponse.Status; +import org.eclipse.tracecompass.internal.provisional.tmf.core.response.TmfModelResponse; +import org.eclipse.tracecompass.segmentstore.core.ISegment; +import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException; +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.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.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> { + + /** + * 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(); + + private static class TidInformation { + private final HostThread fTid; + private final long fStart; + private final long fEnd; + 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); + String label = state.getLabel(); + if (label != null) { + return new TimeGraphState(start, end - start, state.getValue(), label); + } + return new TimeGraphState(start, end - start, state.getValue()); + } + 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(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 ReentrantReadWriteLock fLock = new ReentrantReadWriteLock(false); + private @Nullable TmfModelResponse<List<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; + resetFunctionNames(new NullProgressMonitor()); + } + + @Override + public TmfModelResponse<List<ITimeGraphArrow>> fetchArrows(TimeQueryFilter filter, @Nullable IProgressMonitor monitor) { + // TODO Implement + return new TmfModelResponse<>(null, Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + + @Override + public TmfModelResponse<Map<String, String>> fetchTooltip(SelectionTimeQueryFilter filter, @Nullable IProgressMonitor monitor) { + try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameChartDataProvider#fetchTooltip") //$NON-NLS-1$ + .setCategory(getClass().getSimpleName()).build()) { + Map<Long, FlameChartEntryModel> entries = getSelectedEntries(filter); + 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, filter, monitor); + + return new TmfModelResponse<>(tooltip, Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + } + + private @Nullable Map<String, String> getTooltip(Entry<Long, FlameChartEntryModel> entry, SelectionTimeQueryFilter filter, @Nullable IProgressMonitor monitor) { + FlameChartEntryModel value = Objects.requireNonNull(entry.getValue()); + switch (value.getEntryType()) { + case FUNCTION: + { + CallStackDepth selectedDepth = fIdToCallstack.get(entry.getKey()); + if (selectedDepth == null) { + return null; + } + Multimap<CallStackDepth, ISegment> csFunctions = fFcProvider.queryCallStacks(Collections.singleton(selectedDepth), Collections.singleton(filter.getStart())); + 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)); + } + // 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(entry.getKey()); + if (csId == null) { + return null; + } + CallStackDepth selectedDepth = fIdToCallstack.get(csId); + if (selectedDepth == null) { + return null; + } + int threadId = selectedDepth.getCallStack().getThreadId(filter.getStart()); + ThreadData threadData = fThreadData; + if (threadData == null) { + return null; + } + return threadData.fetchTooltip(threadId, filter.getStart(), 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<List<FlameChartEntryModel>> fetchTree(TimeQueryFilter filter, @Nullable IProgressMonitor monitor) { + if (fCached != null) { + return fCached; + } + + fLock.writeLock().lock(); + 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, 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)); + if (complete) { + TmfModelResponse<List<FlameChartEntryModel>> response = new TmfModelResponse<>(tree, + ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + fCached = response; + return response; + } + return new TmfModelResponse<>(tree, ITmfResponse.Status.RUNNING, CommonStatusMessage.RUNNING); + } finally { + fLock.writeLock().unlock(); + } + } + + 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<List<ThreadEntryModel>> threadTreeResp = dataProvider.fetchTree(new TimeQueryFilter(start, Long.MAX_VALUE, 2), monitor); + List<ThreadEntryModel> threadTree = threadTreeResp.getModel(); + if (threadTree != null) { + fThreadData = new ThreadData(dataProvider, threadTree, threadTreeResp.getStatus()); + break; + } + } + } + } + + private boolean processCallStackElement(ICallStackElement element, Builder<FlameChartEntryModel> builder, FlameChartEntryModel parentEntry) { + + long elementId = getEntryId(element); + FlameChartEntryModel entry = new FlameChartEntryModel(elementId, parentEntry.getId(), element.getName(), parentEntry.getStartTime(), parentEntry.getEndTime(), FlameChartEntryModel.EntryType.LEVEL); + builder.add(entry); + + 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(); + for (int depth = 0; depth < callStack.getMaxDepth(); depth++) { + FlameChartEntryModel flameChartEntry = new FlameChartEntryModel(getEntryId(new CallStackDepth(callStack, depth + 1)), entry.getId(), element.getName(), parentEntry.getStartTime(), parentEntry.getEndTime(), + FlameChartEntryModel.EntryType.FUNCTION, depth + 1); + builder.add(flameChartEntry); + if (depth == 0 && callStack.hasKernelStatuses()) { + needsKernel = true; + builder.add(new FlameChartEntryModel(getKernelEntryId(flameChartEntry.getId()), entry.getId(), String.valueOf(Messages.FlameChartDataProvider_KernelStatusTitle), parentEntry.getStartTime(), parentEntry.getEndTime(), FlameChartEntryModel.EntryType.KERNEL)); + } + } + return needsKernel; + } + // Intermediate element, process children + for (ICallStackElement child : element.getChildren()) { + needsKernel |= processCallStackElement(child, builder, entry); + } + return needsKernel; + } + + // Get the selected entries with the quark + private BiMap<Long, FlameChartEntryModel> getSelectedEntries(SelectionTimeQueryFilter filter) { + fLock.readLock().lock(); + try { + BiMap<Long, FlameChartEntryModel> selectedEntries = HashBiMap.create(); + + for (Long selectedItem : filter.getSelectedItems()) { + FlameChartEntryModel entryModel = fEntries.get(selectedItem); + if (entryModel != null) { + selectedEntries.put(selectedItem, entryModel); + } + } + return selectedEntries; + } finally { + fLock.readLock().unlock(); + } + } + + @Override + public TmfModelResponse<List<ITimeGraphRowModel>> fetchRowModel(SelectionTimeQueryFilter filter, @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(filter); + if (entries.size() == 1 && filter.getTimesRequested().length == 2) { + // this is a request for a follow event. + Entry<@NonNull Long, @NonNull FlameChartEntryModel> entry = entries.entrySet().iterator().next(); + if (filter.getStart() == Long.MIN_VALUE) { + return new TmfModelResponse<>(getFollowEvent(entry, filter.getEnd(), false), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + } else if (filter.getEnd() == Long.MAX_VALUE) { + return new TmfModelResponse<>(getFollowEvent(entry, filter.getStart(), true), 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(filter, 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<>(collect, complete ? Status.COMPLETED : Status.RUNNING, + complete ? CommonStatusMessage.COMPLETED : CommonStatusMessage.RUNNING); + } catch (IndexOutOfBoundsException | TimeRangeException | StateSystemDisposedException e) { + return new TmfModelResponse<>(null, Status.FAILED, String.valueOf(e.getMessage())); + } + } + + private void addRequiredCallstacks(Map<Long, FlameChartEntryModel> entries) { + fLock.readLock().lock(); + try { + 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); + } finally { + fLock.readLock().unlock(); + } + } + + private @Nullable Map<Long, List<ITimeGraphState>> getCallStackRows(SelectionTimeQueryFilter filter, Map<Long, FlameChartEntryModel> entries, SubMonitor subMonitor) throws IndexOutOfBoundsException, TimeRangeException, StateSystemDisposedException { + + // 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); + } + } + + long[] timesRequested = filter.getTimesRequested(); + // Prepare the list of times + List<Long> times = new ArrayList<>(); + for (long time : timesRequested) { + times.add(time); + } + Collections.sort(times); + Multimap<CallStackDepth, ISegment> csFunctions = fFcProvider.queryCallStacks(csEntries.values(), times); + + for (Map.Entry<Long, CallStackDepth> entry : csEntries.entrySet()) { + if (subMonitor.isCanceled()) { + return null; + } + Collection<ISegment> states = csFunctions.get(entry.getValue()); + + // Create the time graph states for this row + List<ITimeGraphState> eventList = new ArrayList<>(states.size()); + states.forEach(state -> eventList.add(createTimeGraphState(state))); + 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, subMonitor)); + } + subMonitor.worked(1); + return rows; + } + + private Map<Long, List<ITimeGraphState>> getKernelStates(List<TidInformation> tids, List<Long> times, 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<List<ITimeGraphRowModel>> rowModel = threadData.fThreadDataProvider.fetchRowModel(tidFilter, monitor); + List<ITimeGraphRowModel> rowModels = rowModel.getModel(); + if (rowModel.getStatus().equals(Status.CANCELLED) || rowModel.getStatus().equals(Status.FAILED) || rowModels == null) { + return Collections.emptyMap(); + } + return mapThreadStates(rowModels, threadModelIds, tids); + } + + private static Map<Long, List<ITimeGraphState>> mapThreadStates(List<ITimeGraphRowModel> rowModels, BiMap<Long, Integer> threadModelIds, List<TidInformation> tids) { + 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) { + 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)) { + states.add(tidInfo.sanitize(state)); + } + 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; + Object value = function.getSymbol(); + Integer pid = function.getProcessId(); + String name = String.valueOf(fTimeEventNames.getUnchecked(new Pair<>(pid, function))); + return new TimeGraphState(function.getStart(), function.getLength(), value.hashCode(), name); + } + + /** + * 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; + } + +} |