From 6814994c4d15c8ea4c1f69eff6c3f4ca857fc313 Mon Sep 17 00:00:00 2001 From: Marco Miller Date: Wed, 25 Jan 2023 14:51:32 -0500 Subject: [PATCH] callstack: Bring FlameChartView UI from Incubator Along with its minimally related dependencies and likely users. This is based on the current understanding of the latter, which may change next. FlameChartView requires FlameChartDataProvider, which in turn requires FlameChartArrowProvider. Both require FlameChartEntryModel. Include all of these, brought from Incubator, consistently. Also include the few currently necessary message and icon resources. Strictly make callstack.ui an x-friend of callstack.core.instrumented, so the former bundle's new classes may use the latter's, herein. For now, keep such new API exposure as restricted as possible. This access may open up as eventually required. CallStackAnalysisListener is a plugin.xml element, along with FlameChartView which gets used by the former. Access to this new view from the UI is to be proven next, fixing that access as necessary then. Keep this early Java packaging as minimal as possible. Refactoring some current package decisions remains likely through follow-up changes as they become relevant. [Added] o.e.t.i.a.callstack.ui.FlameChartView Change-Id: I4d70d12dc9b83bdcfde948a963b6397a1ea4297d Signed-off-by: Marco Miller Reviewed-on: https://git.eclipse.org/r/c/tracecompass/org.eclipse.tracecompass/+/199583 Tested-by: Trace Compass Bot Tested-by: Bernd Hufmann Reviewed-by: Bernd Hufmann --- .../META-INF/MANIFEST.MF | 3 +- .../instrumented/FlameChartArrowProvider.java | 78 ++ .../instrumented/FlameChartDataProvider.java | 870 ++++++++++++++++++ .../instrumented/FlameChartEntryModel.java | 278 ++++++ .../callstack/core/instrumented/Messages.java | 39 + .../core/instrumented/messages.properties | 14 + .../META-INF/MANIFEST.MF | 10 +- .../build.properties | 2 +- .../icons/obj16/binaries_obj.gif | Bin 0 -> 605 bytes .../icons/obj16/stckframe_obj.gif | Bin 0 -> 104 bytes .../icons/obj16/thread_obj.gif | Bin 0 -> 538 bytes .../plugin.properties | 2 + .../plugin.xml | 19 +- .../ui/CallStackAnalysisListener.java | 35 + .../analysis/callstack/ui/FlameChartView.java | 704 ++++++++++++++ .../analysis/callstack/ui/Messages.java | 41 + .../analysis/callstack/ui/messages.properties | 23 + .../META-INF/MANIFEST.MF | 6 +- .../META-INF/MANIFEST.MF | 2 +- .../META-INF/MANIFEST.MF | 4 +- .../META-INF/MANIFEST.MF | 2 +- 21 files changed, 2123 insertions(+), 9 deletions(-) create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartArrowProvider.java create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartDataProvider.java create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartEntryModel.java create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/Messages.java create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/messages.properties create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.ui/icons/obj16/binaries_obj.gif create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.ui/icons/obj16/stckframe_obj.gif create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.ui/icons/obj16/thread_obj.gif create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/CallStackAnalysisListener.java create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/FlameChartView.java create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/Messages.java create mode 100644 analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/messages.properties diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/META-INF/MANIFEST.MF b/analysis/org.eclipse.tracecompass.analysis.callstack.core/META-INF/MANIFEST.MF index 3e177c91f1..a117c06547 100644 --- a/analysis/org.eclipse.tracecompass.analysis.callstack.core/META-INF/MANIFEST.MF +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/META-INF/MANIFEST.MF @@ -18,7 +18,8 @@ Require-Bundle: org.eclipse.core.runtime, org.eclipse.tracecompass.datastore.core, org.eclipse.tracecompass.segmentstore.core, org.eclipse.tracecompass.tmf.core -Export-Package: org.eclipse.tracecompass.internal.analysis.callstack.core;x-friends:="org.eclipse.tracecompass.analysis.callstack.core.tests" +Export-Package: org.eclipse.tracecompass.internal.analysis.callstack.core;x-friends:="org.eclipse.tracecompass.analysis.callstack.core.tests", + org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented;x-friends:="org.eclipse.tracecompass.analysis.callstack.ui" Import-Package: com.google.common.annotations, com.google.common.base, com.google.common.cache, diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartArrowProvider.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartArrowProvider.java new file mode 100644 index 0000000000..425348fe29 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartArrowProvider.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils; +import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; +import org.eclipse.tracecompass.tmf.core.model.filters.TimeQueryFilter; +import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; +import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils; + +/** + * Provide arrows for a flame chart. These arrows may come from any other flame + * chart analysis. + * + * @author Geneviève Bastien + */ +public class FlameChartArrowProvider { + private final ITmfTrace fTrace; + + /** + * Constructor + * + * @param trace + * The trace for which this data provider applies + */ + public FlameChartArrowProvider(ITmfTrace trace) { + fTrace = trace; + } + + /** + * Fetch arrows with query + * + * @param fetchParameters + * the parameters + * @param monitor + * the monitor + * @return the corresponding state intervals + */ + public List fetchArrows(Map fetchParameters, @Nullable IProgressMonitor monitor) { + TimeQueryFilter filter = FetchParametersUtils.createTimeQuery(fetchParameters); + if (filter == null) { + return Collections.emptyList(); + } + long start = filter.getStart(); + long end = filter.getEnd(); + + InstrumentedCallStackAnalysis csModule = null; + Iterable modules = TmfTraceUtils.getAnalysisModulesOfClass(fTrace, InstrumentedCallStackAnalysis.class); + Iterator iterator = modules.iterator(); + + List<@NonNull ITmfStateInterval> allEdges = new ArrayList<>(); + while (iterator.hasNext()) { + csModule = iterator.next(); + List<@NonNull ITmfStateInterval> moduleEdges = csModule.getLinks(start, end, monitor == null ? new NullProgressMonitor() : monitor); + allEdges.addAll(moduleEdges); + } + return allEdges; + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartDataProvider.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartDataProvider.java new file mode 100644 index 0000000000..54d286b7a5 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartDataProvider.java @@ -0,0 +1,870 @@ +/******************************************************************************* + * 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 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented; + +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.internal.analysis.callstack.core.CallStack; +import org.eclipse.tracecompass.internal.analysis.callstack.core.CallStackDepth; +import org.eclipse.tracecompass.internal.analysis.callstack.core.CallStackSeries; +import org.eclipse.tracecompass.internal.analysis.callstack.core.base.FlameDefaultPalette; +import org.eclipse.tracecompass.internal.analysis.callstack.core.base.FlameWithKernelPalette; +import org.eclipse.tracecompass.internal.analysis.callstack.core.base.ICallStackElement; +import org.eclipse.tracecompass.internal.analysis.callstack.core.callgraph.ICalledFunction; +import org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented.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.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.AbstractTmfTraceDataProvider; +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.TimeGraphEntryModel; +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 + */ +public class FlameChartDataProvider extends AbstractTmfTraceDataProvider implements ITimeGraphDataProvider, IOutputStyleProvider { + + /** + * Provider ID. + */ + public static final String ID = "org.eclipse.tracecompass.analysis.callstack.core.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 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 fLinkedEntries = HashBiMap.create(); + private final Collection fProviders = new ArrayList<>(); + private final BiMap fIdToCallstack = HashBiMap.create(); + private final BiMap fIdToElement = HashBiMap.create(); + private final long fTraceId = ENTRY_ID.getAndIncrement(); + + /** Cache for entry metadata */ + private final Map> 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 fThreadTree = new ArrayList<>(); + private final Status fStatus; + + public ThreadData(ThreadStatusDataProvider dataProvider, List threadTree, Status status) { + fThreadDataProvider = dataProvider; + for (TimeGraphEntryModel model : threadTree) { + if (model instanceof ThreadEntryModel) { + fThreadTree.add((ThreadEntryModel) model); + } + } + fStatus = status; + } + + public @Nullable Map fetchTooltip(int threadId, long time, @Nullable IProgressMonitor monitor) { + for (ThreadEntryModel entry : fThreadTree) { + if (entry.getThreadId() == threadId && entry.getStartTime() <= time && entry.getEndTime() >= time) { + TmfModelResponse> tooltip = fThreadDataProvider.fetchTooltip(FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(Collections.singletonList(time), Collections.singleton(entry.getId()))), + monitor); + return tooltip.getModel(); + } + } + return null; + } + } + + private final LoadingCache, @Nullable String> fTimeEventNames = Objects.requireNonNull(CacheBuilder.newBuilder() + .maximumSize(1000) + .build(new CacheLoader, @Nullable String>() { + @Override + public @Nullable String load(Pair 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> 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> fetchArrows(Map fetchParameters, @Nullable IProgressMonitor monitor) { + List arrows = fArrowProvider.fetchArrows(fetchParameters, monitor); + if (monitor != null && monitor.isCanceled()) { + return new TmfModelResponse<>(null, Status.CANCELLED, CommonStatusMessage.TASK_CANCELLED); + } + if (arrows.isEmpty()) { + return new TmfModelResponse<>(Collections.emptyList(), Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + List tgArrows = new ArrayList<>(); + // First, get the distinct callstacks + List csList = new ArrayList<>(); + synchronized (fIdToCallstack) { + // Quick copy the values to a list to avoid keeping the lock for too + // long, adding to a hashSet takes time to calculate the CallStack's + // hash. + csList.addAll(fIdToCallstack.values()); + } + Set callstacks = new HashSet<>(); + for (CallStackDepth csd : csList) { + callstacks.add(csd.getCallStack()); + } + + // 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 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); + CallStackDepth csd = new CallStackDepth(callstack, currentDepth); + synchronized (fIdToCallstack) { + return fIdToCallstack.inverse().get(csd); + } + } + return null; + } + + @Override + public TmfModelResponse> fetchTooltip(Map fetchParameters, @Nullable IProgressMonitor monitor) { + try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameChartDataProvider#fetchTooltip") //$NON-NLS-1$ + .setCategory(getClass().getSimpleName()).build()) { + List times = DataProviderParameterUtils.extractTimeRequested(fetchParameters); + if (times == null || times.isEmpty()) { + // No time specified + return new TmfModelResponse<>(null, Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + Map 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 tooltip = getTooltip(entry.getKey(), entry.getValue(), times.get(0), monitor); + + return new TmfModelResponse<>(tooltip, Status.COMPLETED, CommonStatusMessage.COMPLETED); + } + } + + private @Nullable Map 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 csFunctions = fFcProvider.queryCallStacks(Collections.singleton(selectedDepth), Collections.singleton(time)); + Collection 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 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: + case TRACE: + default: + return null; + } + } + + @Override + public String getId() { + return ID + ':' + fAnalysisId; + } + + // Get an entry for a quark + private long getEntryId(CallStackDepth stack) { + synchronized (fIdToCallstack) { + 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> fetchTree(Map 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 = Math.max(start, fcProvider.getEnd()); + + // Initialize the first element of the tree + ImmutableList.Builder builder = ImmutableList.builder(); + @SuppressWarnings("null") + 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 tree = builder.build(); + tree.forEach(entry -> fEntries.put(entry.getId(), entry)); + + for (FlameChartEntryModel model : tree) { + fEntryMetadata.put(model.getId(), model.getMetadata()); + } + + if (complete) { + TmfModelResponse> 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 == Status.COMPLETED) { + return; + } + // FIXME: Wouldn't work correctly if trace is an experiment as it would + // cover many hosts + Set tracesForHost = TmfTraceManager.getInstance().getTracesForHost(getTrace().getHostId()); + for (ITmfTrace trace : tracesForHost) { + ThreadStatusDataProvider dataProvider = DataProviderManager.getInstance().getOrCreateDataProvider(trace, ThreadStatusDataProvider.ID, ThreadStatusDataProvider.class); + if (dataProvider != null) { + // Get the tree for the trace's current range + TmfModelResponse> threadTreeResp = dataProvider.fetchTree(FetchParametersUtils.timeQueryToMap(new TimeQueryFilter(start, Long.MAX_VALUE, 2)), monitor); + TmfTreeModel threadTree = threadTreeResp.getModel(); + if (threadTree != null) { + fThreadData = new ThreadData(dataProvider, threadTree.getEntries(), threadTreeResp.getStatus()); + break; + } + } + } + } + + private boolean processCallStackElement(ICallStackElement element, Builder 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 getSelectedEntries(Map fetchParameters) { + BiMap selectedEntries = HashBiMap.create(); + + List 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 fetchRowModel(Map fetchParameters, @Nullable IProgressMonitor monitor) { + try (FlowScopeLog scope = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameChartDataProvider#fetchRowModel") //$NON-NLS-1$ + .setCategory(getClass().getSimpleName()).build()) { + + Map entries = getSelectedEntries(fetchParameters); + List 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 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 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> 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 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 entries) { + Map 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); + } + + @SuppressWarnings("null") + private @Nullable Map> getCallStackRows(Map fetchParameters, Map entries, SubMonitor subMonitor) + throws IndexOutOfBoundsException, TimeRangeException { + + // Get the data for the model entries that are of type function + Map> rows = new HashMap<>(); + List tids = new ArrayList<>(); + Map csEntries = new HashMap<>(); + for (Entry entry : entries.entrySet()) { + CallStackDepth selectedDepth = fIdToCallstack.get(entry.getKey()); + if (selectedDepth != null && entry.getValue().getEntryType() == EntryType.FUNCTION) { + csEntries.put(entry.getKey(), selectedDepth); + } + } + + List times = DataProviderParameterUtils.extractTimeRequested(fetchParameters); + Collections.sort(times); + Multimap 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 entry : csEntries.entrySet()) { + if (subMonitor.isCanceled()) { + return null; + } + Collection states = csFunctions.get(entry.getValue()); + Long key = Objects.requireNonNull(entry.getKey()); + + // Create the time graph states for this row + List 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> getKernelStates(List tids, List times, Map>> predicates, SubMonitor monitor) { + // Get the thread statuses from the thread status provider + ThreadData threadData = fThreadData; + if (threadData == null) { + return Collections.emptyMap(); + } + List 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 threadModelIds = filterThreads(tree, tids); + SelectionTimeQueryFilter tidFilter = new SelectionTimeQueryFilter(times, threadModelIds.keySet()); + TmfModelResponse rowModel = threadData.fThreadDataProvider.fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(tidFilter), monitor); + TimeGraphModel rowModels = rowModel.getModel(); + if (rowModel.getStatus() == Status.CANCELLED || rowModel.getStatus() == Status.FAILED || rowModels == null) { + return Collections.emptyMap(); + } + return mapThreadStates(rowModels.getRows(), threadModelIds, tids, predicates, monitor); + } + + private Map> mapThreadStates(List rowModels, BiMap threadModelIds, List tids, Map>> predicates, SubMonitor monitor) { + ImmutableMap statusRows = Maps.uniqueIndex(rowModels, m -> m.getEntryID()); + // Match the states of thread status to the requested tid lines + Long prevId = -1L; + List states = null; + Map> 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 (!Objects.equals(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 filterThreads(List model, List tids) { + // Get the entry model IDs that match requested tids + BiMap tidEntries = HashBiMap.create(); + Set 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 getKernelTids(CallStackDepth callStackDepth, Collection states, Long linked) { + List 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 getFollowEvent(Entry 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: + case LEVEL: + case TRACE: + default: + 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 tooltip = getTooltip(entryId, entryModel, time, monitor); + if (tooltip == null) { + return data; + } + for (Entry entry : tooltip.entrySet()) { + data.put(entry.getKey(), entry.getValue()); + } + return data; + } + + @Override + public TmfModelResponse fetchStyle(Map fetchParameters, @Nullable IProgressMonitor monitor) { + // Use the palette with kernel styles, at worst, kernel styles won't be + // used + Map styles = FlameWithKernelPalette.getInstance().getStyles(); + return new TmfModelResponse<>(new OutputStyleModel(styles), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED); + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartEntryModel.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartEntryModel.java new file mode 100644 index 0000000000..b6da4cb353 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/FlameChartEntryModel.java @@ -0,0 +1,278 @@ +/********************************************************************** + * 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 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +package org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.analysis.os.linux.core.model.HostThread; +import org.eclipse.tracecompass.analysis.os.linux.core.model.OsStrings; +import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphEntryModel; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +/** + * {@link TimeGraphEntryModel} for the Flame chart data + * + * @author Geneviève Bastien + */ +public class FlameChartEntryModel extends TimeGraphEntryModel { + + /** + * An enumeration for the type of flame chart entries + */ + public enum EntryType { + /** + * A descriptive entry, for example the one for the trace + */ + TRACE, + /** + * Represent a group of the callstack analysis + */ + LEVEL, + /** + * Represent an entry with function data, the actual callstack data + */ + FUNCTION, + /** + * This entry will show the kernel statuses for the TID running the + * callstack. Will not always be present + */ + KERNEL + } + + /** + * A builder class for this entry model + */ + public static class Builder { + private final long fId; + private final long fParentId; + private final long fStartTime; + private final String fName; + private final EntryType fEntryType; + private final int fDepth; + private long fEndTime; + private @Nullable HostThread fHostThread; + + /** + * Constructor + * + * @param id + * The unique ID for this Entry model for its trace + * @param parentId + * The unique ID of the parent entry model + * @param name + * The name of this entry + * @param start + * the thread's start time + * @param entryType + * The type of this entry + * @param depth + * The depth if the entry is a function entry, can be + * -1 if no depth + */ + public Builder(long id, long parentId, String name, long start, EntryType entryType, int depth) { + fId = id; + fParentId = parentId; + fName = name; + fStartTime = start; + fEntryType = entryType; + fDepth = depth; + fEndTime = start; + fHostThread = null; + } + + /** + * Get the unique ID for this entry / builder + * + * @return this entry's unique ID + */ + public long getId() { + return fId; + } + + /** + * Get this entry / builder's start time + * + * @return the start time + */ + public long getStartTime() { + return fStartTime; + } + + /** + * Get this entry/builder's end time + * + * @return the end time + */ + public long getEndTime() { + return fEndTime; + } + + /** + * Update this entry / builder's end time + * + * @param endTime + * the new end time + */ + public void setEndTime(long endTime) { + fEndTime = Long.max(fEndTime, endTime); + } + + /** + * Update this entry / builder's HostThread + * + * @param hostThread + * the new HostThread + */ + public void setHostThread(@Nullable HostThread hostThread) { + fHostThread = hostThread; + } + + /** + * Build the {@link FlameChartEntryModel} from the builder, specify the + * parent id here to avoid race conditions + * + * @return the relevant {@link FlameChartEntryModel} + */ + public FlameChartEntryModel build() { + return new FlameChartEntryModel(fId, fParentId, Collections.singletonList(fName), fStartTime, fEndTime, fEntryType, fDepth, fHostThread); + } + + /** + * Get the parent ID of this entry + * + * @return The parent ID + */ + public long getParentId() { + return fParentId; + } + + /** + * Get the depth of this entry + * + * @return The depth + */ + public int getDepth() { + return fDepth; + } + } + + private final EntryType fEntryType; + private final int fDepth; + private @Nullable HostThread fHostThread; + + /** + * Constructor + * + * @param id + * unique ID for this {@link FlameChartEntryModel} + * @param parentId + * parent's ID to build the tree + * @param name + * entry's name + * @param startTime + * entry's start time + * @param endTime + * entry's end time + * @param entryType + * The type of this entry + */ + public FlameChartEntryModel(long id, long parentId, List name, long startTime, long endTime, EntryType entryType) { + super(id, parentId, name, startTime, endTime); + fEntryType = entryType; + fDepth = -1; + } + + /** + * Constructor + * + * @param elementId + * unique ID for this {@link FlameChartEntryModel} + * @param parentId + * parent's ID to build the tree + * @param name + * entry's name + * @param startTime + * entry's start time + * @param endTime + * entry's end time + * @param entryType + * The type of this entry + * @param depth + * The depth this entry represents + * @param hostThread + * The entry's unique hostThread or null if host + * thread not available or variable + */ + public FlameChartEntryModel(long elementId, long parentId, List name, long startTime, long endTime, EntryType entryType, int depth, @Nullable HostThread hostThread) { + super(elementId, parentId, name, startTime, endTime); + fEntryType = entryType; + fDepth = depth; + fHostThread = hostThread; + } + + /** + * Getter for the entry type + * + * @return The type of entry. + */ + public EntryType getEntryType() { + return fEntryType; + } + + /** + * Get the depth of this entry + * + * @return The depth of this entry + */ + public int getDepth() { + return fDepth; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!super.equals(obj)) { + // nullness, class, name, ids + return false; + } + if (!(obj instanceof FlameChartEntryModel)) { + return false; + } + FlameChartEntryModel other = (FlameChartEntryModel) obj; + return fEntryType == other.fEntryType; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), fEntryType); + } + + @Override + public String toString() { + return super.toString() + ' ' + fEntryType.toString(); + } + + @Override + public Multimap getMetadata() { + Multimap map = HashMultimap.create(); + HostThread hostThread = fHostThread; + if (hostThread != null) { + map.put(OsStrings.tid(), hostThread.getTid()); + map.put("hostId", hostThread.getHost()); //$NON-NLS-1$ + } + return map; + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/Messages.java b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/Messages.java new file mode 100644 index 0000000000..71f45e215f --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/Messages.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * 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 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.osgi.util.NLS; + +/** + * Message bundle for the call stack state provider. + */ +public class Messages extends NLS { + private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$ + + /** Title of the symbol tooltip key */ + public static @Nullable String FlameChartDataProvider_Symbol; + + /** Title of the thread ID tooltip key */ + public static @Nullable String FlameChartDataProvider_ThreadId; + + /** Title of kernel status rows */ + public static @Nullable String FlameChartDataProvider_KernelStatusTitle; + + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/messages.properties b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/messages.properties new file mode 100644 index 0000000000..16132f1be4 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.core/src/org/eclipse/tracecompass/internal/analysis/callstack/core/instrumented/messages.properties @@ -0,0 +1,14 @@ +############################################################################### +# 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 2.0 +# which accompanies this distribution, and is available at +# https://www.eclipse.org/legal/epl-2.0 +# +# SPDX-License-Identifier: EPL-2.0 +############################################################################### + +FlameChartDataProvider_Symbol=Symbol +FlameChartDataProvider_ThreadId=TID +FlameChartDataProvider_KernelStatusTitle=Kernel statuses diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/META-INF/MANIFEST.MF b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/META-INF/MANIFEST.MF index 80db70dffb..e879f3a8f4 100644 --- a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/META-INF/MANIFEST.MF +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/META-INF/MANIFEST.MF @@ -9,7 +9,15 @@ Bundle-Activator: org.eclipse.tracecompass.internal.analysis.callstack.ui.Activa Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Automatic-Module-Name: org.eclipse.tracecompass.analysis.callstack.ui -Require-Bundle: org.eclipse.core.runtime, +Require-Bundle: org.eclipse.core.resources, + org.eclipse.core.runtime, org.eclipse.jdt.annotation;bundle-version="[2.0.0,3.0.0)";resolution:=optional, + org.eclipse.tracecompass.analysis.callstack.core, + org.eclipse.tracecompass.analysis.os.linux.core, + org.eclipse.tracecompass.analysis.os.linux.ui, + org.eclipse.tracecompass.analysis.timing.core, + org.eclipse.tracecompass.tmf.core, + org.eclipse.tracecompass.tmf.ui, org.eclipse.ui Export-Package: org.eclipse.tracecompass.internal.analysis.callstack.ui;x-internal:=true +Import-Package: com.google.common.collect diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/build.properties b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/build.properties index 87ad25d65e..5d19fd102d 100644 --- a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/build.properties +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/build.properties @@ -14,6 +14,6 @@ output.. = bin/ bin.includes = META-INF/,\ .,\ about.html,\ + icons/,\ plugin.properties,\ plugin.xml - # TODO icons/,\ diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/icons/obj16/binaries_obj.gif b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/icons/obj16/binaries_obj.gif new file mode 100644 index 0000000000000000000000000000000000000000..5a08512dcb19e3c6002e363e7cfd030f1bf859f3 GIT binary patch literal 605 zcmZ?wbhEHb6krfwc*el+@8AEmYu6n;_2lE{U)v8oK6vKIwgcBsU3_r!@%OvWf4}?u zw{7;ps}H~3div|$vo9fOjU5XP9XfmO(v|BW8BLQ{o_PQ1+v8`i9Kwqn!wL@G_;U2t zm)5?S%T}#_@Z@D|*<@++5MB2qpX93Wytd%XhOCYTak z`1xzM9=$w&?asv;_gspmCHJn)nz+s}JR`cOrF-qMt~E!eZabgVI4QJ#c67^voC&L% zr!8K*are>g!oa?(A*ZRiIXGCdB_}I8BPTOED<{}J*hz|mD<`8T zrzd-oo11H}7w4=w^Ckra1_T5JFYjF$92ycDY{ffERXImZdSY;Ru%{FcUlW@!vzU2P zTSv2nj+TRf0;`64XEVE5YJ{b?9KVlUr;@BlSaMplE{nB)r@e~2V|rw4jHwXg#BQmL zo(~^&i|Hq1BvgFNoP0olJE4QqIgOQFo!3(FVc^529!bNhB}ED>wxi L?r^X&F<1itU`rGx literal 0 HcmV?d00001 diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/icons/obj16/stckframe_obj.gif b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/icons/obj16/stckframe_obj.gif new file mode 100644 index 0000000000000000000000000000000000000000..f1e585bdf727a4a0c6b99516d0eacb6dff499214 GIT binary patch literal 104 zcmZ?wbhEHb6krfwSj4~}5HlydXIEIy&Tt^=-FfcbtN;K1GcYhH{$ycfU|?g=0kVNA z1sE8Zbu)I|`Nwy{Q2 z;r)${54U?h-s<^qv-iub314pXZCKX(=0w%oleO!Y^sQUmzjn#w$D2}L?aqC*EAROJ zO(za)ezH0J@y3kTyYgP`D(Eis=`IdDwtwsK{ad;V1G)->dkTZQ3xZGW+i-S9N^e2X z>HV8e?Avl`&*rj((9*<+l7#TJZKWI9%GS4*u4}I-PlzZ@j4DZp+|X9BvAuF_d)3C) zicM{mo7$>3wpNuVM3u!ymc&Pu$3>RMN0-G#{r~@;VMqbRpDc_F46Y11AXkIpgn_-h z!L_NmrM0cSquJ2jy3^IjMZ=oOM90*wU7u0ijM-k5!K__bft{U=g;hnV&DzMBQ&eA! z!@=0L)qq=oPoK-4ho4uk)xy$ISj0?F$jri|O-fN-QbJx%O}E`%R?A$*UR~Uv-O5SZ VMAKT@+@{asT;He9Upg8YtN}+3r}h8< literal 0 HcmV?d00001 diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/plugin.properties b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/plugin.properties index 607c3e8829..1eebdb5196 100644 --- a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/plugin.properties +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/plugin.properties @@ -11,3 +11,5 @@ Bundle-Vendor = Eclipse Trace Compass Bundle-Name = Trace Compass Analysis Callstack UI Plug-in + +view.flameChart = Flame Chart (new Callstack) diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/plugin.xml b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/plugin.xml index fa3b8ea1f8..24c761419d 100644 --- a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/plugin.xml +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/plugin.xml @@ -1,7 +1,22 @@ - + + + diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/CallStackAnalysisListener.java b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/CallStackAnalysisListener.java new file mode 100644 index 0000000000..7402847369 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/CallStackAnalysisListener.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2016 É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 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package org.eclipse.tracecompass.internal.analysis.callstack.ui; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented.IFlameChartProvider; +import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule; +import org.eclipse.tracecompass.tmf.core.analysis.ITmfNewAnalysisModuleListener; +import org.eclipse.tracecompass.tmf.ui.analysis.TmfAnalysisViewOutput; + +/** + * Registers the {@link FlameChartView} to {@link IFlameChartProvider}. The + * analysis being an abstract class, it is not possible to use the output + * extension to add the view, but the listener fixes the issue. + * + * @author Geneviève Bastien + */ +public class CallStackAnalysisListener implements ITmfNewAnalysisModuleListener { + + @Override + public void moduleCreated(@Nullable IAnalysisModule module) { + if (module instanceof IFlameChartProvider) { + module.registerOutput(new TmfAnalysisViewOutput(FlameChartView.ID, module.getId())); + } + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/FlameChartView.java b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/FlameChartView.java new file mode 100644 index 0000000000..9799a1ff04 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/FlameChartView.java @@ -0,0 +1,704 @@ +/******************************************************************************* + * Copyright (c) 2013, 2018 Ericsson and others. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package org.eclipse.tracecompass.internal.analysis.callstack.ui; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.tracecompass.analysis.os.linux.core.model.HostThread; +import org.eclipse.tracecompass.analysis.os.linux.core.model.OsStrings; +import org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented.FlameChartDataProvider; +import org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented.FlameChartEntryModel; +import org.eclipse.tracecompass.internal.analysis.callstack.core.instrumented.FlameChartEntryModel.EntryType; +import org.eclipse.tracecompass.internal.analysis.os.linux.ui.actions.FollowThreadAction; +import org.eclipse.tracecompass.internal.provisional.tmf.ui.widgets.timegraph.BaseDataProviderTimeGraphPresentationProvider; +import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils; +import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderManager; +import org.eclipse.tracecompass.tmf.core.model.filters.SelectionTimeQueryFilter; +import org.eclipse.tracecompass.tmf.core.model.timegraph.IElementResolver; +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.TimeGraphEntryModel; +import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphModel; +import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataModel; +import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse; +import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal; +import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler; +import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal; +import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal; +import org.eclipse.tracecompass.tmf.core.signal.TmfWindowRangeUpdatedSignal; +import org.eclipse.tracecompass.tmf.core.symbols.ISymbolProvider; +import org.eclipse.tracecompass.tmf.core.symbols.SymbolProviderManager; +import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; +import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; +import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestampFormat; +import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; +import org.eclipse.tracecompass.tmf.ui.editors.ITmfTraceEditor; +import org.eclipse.tracecompass.tmf.ui.symbols.ISymbolProviderPreferencePage; +import org.eclipse.tracecompass.tmf.ui.symbols.SymbolProviderConfigDialog; +import org.eclipse.tracecompass.tmf.ui.views.TmfViewFactory; +import org.eclipse.tracecompass.tmf.ui.views.timegraph.BaseDataProviderTimeGraphView; +import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphPresentationProvider; +import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphViewer; +import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry; +import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.NamedTimeEvent; +import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry; +import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphControl; +import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.Utils; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchActionConstants; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; + +/** + * Main implementation for the Flame Chart view + * + * @author Patrick Tasse + * @author Geneviève Bastien + */ +@SuppressWarnings("deprecation") +public class FlameChartView extends BaseDataProviderTimeGraphView { + + // ------------------------------------------------------------------------ + // Constants + // ------------------------------------------------------------------------ + + /** View ID. */ + public static final String ID = "org.eclipse.tracecompass.analysis.callstack.ui.flamechart"; //$NON-NLS-1$ + + private static final String[] COLUMN_NAMES = new String[] { + Messages.CallStackView_FunctionColumn, + Messages.CallStackView_DepthColumn, + Messages.CallStackView_EntryTimeColumn, + Messages.CallStackView_ExitTimeColumn, + Messages.CallStackView_DurationColumn + }; + + private static final Comparator DEPTH_COMPARATOR = (o1, o2) -> { + if (o1 instanceof TimeGraphEntry && o2 instanceof TimeGraphEntry) { + TimeGraphEntry t1 = (TimeGraphEntry) o1; + TimeGraphEntry t2 = (TimeGraphEntry) o2; + ITmfTreeDataModel model1 = t1.getEntryModel(); + ITmfTreeDataModel model2 = t2.getEntryModel(); + if (model1 instanceof FlameChartEntryModel && model2 instanceof FlameChartEntryModel) { + FlameChartEntryModel m1 = (FlameChartEntryModel) model1; + FlameChartEntryModel m2 = (FlameChartEntryModel) model2; + if (m1.getEntryType() == EntryType.FUNCTION && m2.getEntryType() == EntryType.FUNCTION) { + return Integer.compare(Integer.valueOf(m1.getName()), Integer.valueOf(m2.getName())); + } + return Integer.compare(m1.getEntryType().ordinal(), m2.getEntryType().ordinal()); + } + } + return 0; + }; + + @SuppressWarnings("unchecked") + private static final Comparator[] COMPARATORS = new Comparator[] { + Comparator.comparing(ITimeGraphEntry::getName), + DEPTH_COMPARATOR, + Comparator.comparingLong(ITimeGraphEntry::getStartTime), + Comparator.comparingLong(ITimeGraphEntry::getEndTime) + }; + + private static final String[] FILTER_COLUMN_NAMES = new String[] { + Messages.CallStackView_ThreadColumn + }; + + private static final Image GROUP_IMAGE = Activator.getDefault().getImageFromPath("icons/obj16/thread_obj.gif"); //$NON-NLS-1$ + private static final Image STACKFRAME_IMAGE = Activator.getDefault().getImageFromPath("icons/obj16/stckframe_obj.gif"); //$NON-NLS-1$ + + private static final String IMPORT_BINARY_ICON_PATH = "icons/obj16/binaries_obj.gif"; //$NON-NLS-1$ + + // ------------------------------------------------------------------------ + // Fields + // ------------------------------------------------------------------------ + + // The next event action + private @Nullable Action fNextEventAction = null; + + // The previous event action + private @Nullable Action fPrevEventAction = null; + + // The action to import a binary file mapping + private @Nullable Action fConfigureSymbolsAction = null; + + // When set to true, syncToTime() will select the first call stack entry + // whose current state start time exactly matches the sync time. + private boolean fSyncSelection = false; + + private final Map fFunctions = new HashMap<>(); + + // ------------------------------------------------------------------------ + // Classes + // ------------------------------------------------------------------------ + + private static class CallStackComparator implements Comparator { + @Override + public int compare(ITimeGraphEntry o1, ITimeGraphEntry o2) { + if (o1 instanceof TimeGraphEntry && o2 instanceof TimeGraphEntry) { + TimeGraphEntry t1 = (TimeGraphEntry) o1; + TimeGraphEntry t2 = (TimeGraphEntry) o2; + return DEPTH_COMPARATOR.compare(t1, t2); + } + return 0; + } + } + + private class CallStackTreeLabelProvider extends TreeLabelProvider { + @Override + public @Nullable Image getColumnImage(@Nullable Object element, int columnIndex) { + if (columnIndex == 0 && (element instanceof TimeGraphEntry)) { + TimeGraphEntry entry = (TimeGraphEntry) element; + ITmfTreeDataModel entryModel = entry.getEntryModel(); + if (entryModel instanceof FlameChartEntryModel) { + FlameChartEntryModel model = (FlameChartEntryModel) entryModel; + if (model.getEntryType() == EntryType.LEVEL) { + // Is this the symbol key image? then show the symbol + // key image, otherwise, just the group image + return GROUP_IMAGE; + } + if (model.getEntryType() == EntryType.FUNCTION && fFunctions.containsKey(entryModel.getId())) { + return STACKFRAME_IMAGE; + } + } + } + return null; + } + + @Override + public @Nullable String getColumnText(@Nullable Object element, int columnIndex) { + if (element instanceof TraceEntry && columnIndex == 0) { + return ((TraceEntry) element).getName(); + } else if (element instanceof TimeGraphEntry) { + TimeGraphEntry entry = (TimeGraphEntry) element; + ITmfTreeDataModel model = entry.getEntryModel(); + ITimeGraphState function = fFunctions.get(model.getId()); + if (columnIndex == 0 && (!(model instanceof FlameChartEntryModel) || + (model instanceof FlameChartEntryModel && ((FlameChartEntryModel) model).getEntryType() != EntryType.FUNCTION))) { + // It is not a function entry + return entry.getName(); + } + + if (function != null) { + if (columnIndex == 0) { + // functions + return function.getLabel(); + } else if (columnIndex == 1 && model instanceof FlameChartEntryModel) { + return entry.getName(); + } else if (columnIndex == 2) { + return TmfTimestampFormat.getDefaulTimeFormat().format(function.getStartTime()); + } else if (columnIndex == 3) { + return TmfTimestampFormat.getDefaulTimeFormat().format(function.getStartTime() + function.getDuration()); + } else if (columnIndex == 4) { + return TmfTimestampFormat.getDefaulIntervalFormat().format(function.getDuration()); + } + } + } + return ""; //$NON-NLS-1$ + } + } + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + /** + * Default constructor + */ + public FlameChartView() { + this(ID, new BaseDataProviderTimeGraphPresentationProvider(), FlameChartDataProvider.ID); + } + + /** + * Custom constructor, used for extending the callstack view with a custom + * presentation provider or data provider. + * + * @param id + * The ID of the view + * @param presentationProvider + * the presentation provider + * @param dataProviderID + * the data provider id + */ + @SuppressWarnings("null") + public FlameChartView(String id, TimeGraphPresentationProvider presentationProvider, String dataProviderID) { + super(id, presentationProvider, dataProviderID); + setTreeColumns(COLUMN_NAMES, COMPARATORS, 0); + setTreeLabelProvider(new CallStackTreeLabelProvider()); + setEntryComparator(new CallStackComparator()); + setFilterColumns(FILTER_COLUMN_NAMES); + setFilterLabelProvider(new CallStackTreeLabelProvider()); + } + + // ------------------------------------------------------------------------ + // ViewPart + // ------------------------------------------------------------------------ + + @Override + public void createPartControl(@Nullable Composite parent) { + super.createPartControl(parent); + + getTimeGraphViewer().addTimeListener(event -> synchingToTime(event.getBeginTime())); + + getTimeGraphViewer().getTimeGraphControl().addMouseListener(new MouseAdapter() { + @Override + public void mouseDoubleClick(@Nullable MouseEvent event) { + ITimeGraphEntry selection = getTimeGraphViewer().getSelection(); + if (!(selection instanceof TimeGraphEntry)) { + // also null checks + return; + } + ITimeGraphState function = fFunctions.get(((TimeGraphEntry) selection).getEntryModel().getId()); + if (function != null) { + long entryTime = function.getStartTime(); + long exitTime = entryTime + function.getDuration(); + TmfTimeRange range = new TmfTimeRange(TmfTimestamp.fromNanos(entryTime), TmfTimestamp.fromNanos(exitTime)); + broadcast(new TmfWindowRangeUpdatedSignal(FlameChartView.this, range, getTrace())); + getTimeGraphViewer().setStartFinishTime(entryTime, exitTime); + startZoomThread(entryTime, exitTime); + } + } + }); + + getTimeGraphViewer().getTimeGraphControl().addMouseListener(new MouseAdapter() { + @Override + public void mouseDoubleClick(@Nullable MouseEvent e) { + TimeGraphControl timeGraphControl = getTimeGraphViewer().getTimeGraphControl(); + ISelection selection = timeGraphControl.getSelection(); + if (selection instanceof IStructuredSelection) { + for (Object object : ((IStructuredSelection) selection).toList()) { + if (object instanceof NamedTimeEvent) { + NamedTimeEvent event = (NamedTimeEvent) object; + long startTime = event.getTime(); + long endTime = startTime + event.getDuration(); + TmfTimeRange range = new TmfTimeRange(TmfTimestamp.fromNanos(startTime), TmfTimestamp.fromNanos(endTime)); + broadcast(new TmfWindowRangeUpdatedSignal(FlameChartView.this, range, getTrace())); + getTimeGraphViewer().setStartFinishTime(startTime, endTime); + startZoomThread(startTime, endTime); + break; + } + } + } + } + }); + + IEditorPart editor = getSite().getPage().getActiveEditor(); + if (editor instanceof ITmfTraceEditor) { + ITmfTrace trace = ((ITmfTraceEditor) editor).getTrace(); + if (trace != null) { + traceSelected(new TmfTraceSelectedSignal(this, trace)); + } + } + } + + /** + * Handler for the selection range signal. + * + * @param signal + * The incoming signal + */ + @Override + @TmfSignalHandler + public void selectionRangeUpdated(final @Nullable TmfSelectionRangeUpdatedSignal signal) { + fSyncSelection = true; + super.selectionRangeUpdated(signal); + } + + @Override + @TmfSignalHandler + public void windowRangeUpdated(final @Nullable TmfWindowRangeUpdatedSignal signal) { + if (signal == null || signal.getSource() == this) { + return; + } + super.windowRangeUpdated(signal); + } + + // ------------------------------------------------------------------------ + // Internal + // ------------------------------------------------------------------------ + + @Override + protected void rebuild() { + super.rebuild(); + updateConfigureSymbolsAction(); + } + + @Override + protected String getProviderId() { + String secondaryId = this.getViewSite().getSecondaryId(); + return (secondaryId == null) ? FlameChartDataProvider.ID : FlameChartDataProvider.ID + ':' + secondaryId.split(TmfViewFactory.INTERNAL_SECONDARY_ID_SEPARATOR)[0]; // NOSONAR + } + + @Override + protected void buildEntryList(final ITmfTrace trace, final ITmfTrace parentTrace, final IProgressMonitor monitor) { + FlameChartDataProvider provider = DataProviderManager + .getInstance().getOrCreateDataProvider(trace, getProviderId(), FlameChartDataProvider.class); + if (provider == null) { + addUnavailableEntry(trace, parentTrace); + return; + } + + provider.resetFunctionNames(monitor); + super.buildEntryList(trace, parentTrace, monitor); + } + + private void addUnavailableEntry(ITmfTrace trace, ITmfTrace parentTrace) { + String name = Messages.CallStackView_StackInfoNotAvailable + ' ' + '(' + trace.getName() + ')'; + TimeGraphEntry unavailableEntry = new TimeGraphEntry(name, 0, 0) { + @Override + public boolean hasTimeEvents() { + return false; + } + }; + addToEntryList(parentTrace, Collections.singletonList(unavailableEntry)); + if (parentTrace == getTrace()) { + refresh(); + } + } + + @Override + protected void synchingToTime(final long time) { + List traceEntries = getEntryList(getTrace()); + if (traceEntries != null) { + for (TraceEntry traceEntry : Iterables.filter(traceEntries, TraceEntry.class)) { + if (traceEntry.getStartTime() >= time) { + continue; + } + // Do not query for kernel entries + Iterable unfiltered = Utils.flatten(traceEntry); + Iterable filtered = Iterables.filter(unfiltered, e -> { + ITmfTreeDataModel model = e.getEntryModel(); + if (model instanceof FlameChartEntryModel) { + return ((FlameChartEntryModel) model).getEntryType() != EntryType.KERNEL; + } + return true; + }); + Map map = Maps.uniqueIndex(filtered, e -> e.getEntryModel().getId()); + // use time -1 as a lower bound for the end of Time events to be + // included. + SelectionTimeQueryFilter filter = new SelectionTimeQueryFilter(Math.max(traceEntry.getStartTime(), time - 1), time, 2, map.keySet()); + TmfModelResponse<@NonNull TimeGraphModel> response = traceEntry.getProvider().fetchRowModel(FetchParametersUtils.selectionTimeQueryToMap(filter), null); + TimeGraphModel model = response.getModel(); + if (model != null) { + for (ITimeGraphRowModel row : model.getRows()) { + syncToRow(row, time, map); + } + } + } + } + fSyncSelection = false; + if (Display.getCurrent() != null) { + getTimeGraphViewer().refresh(); + } + } + + private void syncToRow(ITimeGraphRowModel rowModel, long time, Map entryMap) { + long id = rowModel.getEntryID(); + List<@NonNull ITimeGraphState> list = rowModel.getStates(); + if (!list.isEmpty()) { + ITimeGraphState event = list.get(0); + if (event.getStartTime() + event.getDuration() <= time && list.size() > 1) { + /* + * get the second time graph state as passing time - 1 as a + * first argument to the filter will get the previous state, if + * time is the beginning of an event + */ + event = list.get(1); + } + if (event.getLabel() != null) { + fFunctions.put(id, event); + } else { + fFunctions.remove(id); + } + + if (fSyncSelection && time == event.getStartTime()) { + TimeGraphEntry entry = entryMap.get(id); + if (entry != null) { + fSyncSelection = false; + Display.getDefault().asyncExec(() -> { + getTimeGraphViewer().setSelection(entry, true); + getTimeGraphViewer().getTimeGraphControl().fireSelectionChanged(); + }); + } + } + } else { + fFunctions.remove(id); + } + } + + @Override + protected void fillLocalToolBar(@Nullable IToolBarManager manager) { + if (manager == null) { + return; + } + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, getConfigureSymbolsAction()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, new Separator()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, getTimeGraphViewer().getShowFilterDialogAction()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, new Separator()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, getTimeGraphViewer().getResetScaleAction()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, getPreviousEventAction()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, getNextEventAction()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, new Separator()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, getTimeGraphViewer().getToggleBookmarkAction()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, getTimeGraphViewer().getPreviousMarkerAction()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, getTimeGraphViewer().getNextMarkerAction()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, new Separator()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, getTimeGraphViewer().getPreviousItemAction()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, getTimeGraphViewer().getNextItemAction()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, getTimeGraphViewer().getZoomInAction()); + manager.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, getTimeGraphViewer().getZoomOutAction()); + } + + /** + * Get the the next event action. + * + * @return The action object + */ + @SuppressWarnings("null") + private Action getNextEventAction() { + Action nextEventAction = fNextEventAction; + if (fNextEventAction == null) { + Action nextAction = getTimeGraphViewer().getNextEventAction(); + fNextEventAction = new Action() { + @Override + public void run() { + TimeGraphViewer viewer = getTimeGraphViewer(); + ITimeGraphEntry entry = viewer.getSelection(); + if (entry instanceof TimeGraphEntry) { + TimeGraphEntry callStackEntry = (TimeGraphEntry) entry; + ITimeGraphDataProvider provider = getProvider(callStackEntry); + long selectionBegin = viewer.getSelectionBegin(); + Map<@NonNull String, @NonNull Object> parameters = FetchParametersUtils.selectionTimeQueryToMap(new SelectionTimeQueryFilter(selectionBegin, Long.MAX_VALUE, 2, Collections.singleton(callStackEntry.getEntryModel().getId()))); + TmfModelResponse<@NonNull TimeGraphModel> response = provider.fetchRowModel(parameters, null); + TimeGraphModel model = response.getModel(); + if (model == null || model.getRows().size() != 1) { + return; + } + List<@NonNull ITimeGraphState> row = model.getRows().get(0).getStates(); + if (row.size() != 1) { + return; + } + ITimeGraphState stackInterval = row.get(0); + if (stackInterval.getStartTime() <= selectionBegin && selectionBegin <= stackInterval.getStartTime() + stackInterval.getDuration()) { + viewer.setSelectedTimeNotify(stackInterval.getStartTime() + stackInterval.getDuration() + 1, true); + } else { + viewer.setSelectedTimeNotify(stackInterval.getStartTime(), true); + } + int stackLevel = stackInterval.getValue(); + ITimeGraphEntry selectedEntry = callStackEntry.getParent().getChildren().get(Integer.max(0, stackLevel - 1)); + viewer.setSelection(selectedEntry, true); + viewer.getTimeGraphControl().fireSelectionChanged(); + startZoomThread(viewer.getTime0(), viewer.getTime1()); + } + } + }; + nextEventAction = fNextEventAction; + nextEventAction.setText(nextAction.getText()); + nextEventAction.setToolTipText(nextAction.getToolTipText()); + nextEventAction.setImageDescriptor(nextAction.getImageDescriptor()); + } + return nextEventAction; + } + + /** + * Get the previous event action. + * + * @return The Action object + */ + @SuppressWarnings("null") + private Action getPreviousEventAction() { + Action prevEventAction = fPrevEventAction; + if (fPrevEventAction == null) { + Action prevAction = getTimeGraphViewer().getPreviousEventAction(); + fPrevEventAction = new Action() { + @Override + public void run() { + TimeGraphViewer viewer = getTimeGraphViewer(); + ITimeGraphEntry entry = viewer.getSelection(); + if (entry instanceof TimeGraphEntry) { + TimeGraphEntry callStackEntry = (TimeGraphEntry) entry; + ITimeGraphDataProvider provider = getProvider(callStackEntry); + long selectionBegin = viewer.getSelectionBegin(); + Map<@NonNull String, @NonNull Object> parameters = FetchParametersUtils + .selectionTimeQueryToMap(new SelectionTimeQueryFilter(Lists.newArrayList(Long.MIN_VALUE, selectionBegin), Collections.singleton(callStackEntry.getEntryModel().getId()))); + TmfModelResponse<@NonNull TimeGraphModel> response = provider.fetchRowModel(parameters, null); + TimeGraphModel model = response.getModel(); + if (model == null || model.getRows().size() != 1) { + return; + } + List<@NonNull ITimeGraphState> row = model.getRows().get(0).getStates(); + if (row.size() != 1) { + return; + } + ITimeGraphState stackInterval = row.get(0); + viewer.setSelectedTimeNotify(stackInterval.getStartTime(), true); + int stackLevel = stackInterval.getValue(); + ITimeGraphEntry selectedEntry = callStackEntry.getParent().getChildren().get(Integer.max(0, stackLevel - 1)); + viewer.setSelection(selectedEntry, true); + viewer.getTimeGraphControl().fireSelectionChanged(); + startZoomThread(viewer.getTime0(), viewer.getTime1()); + } + } + }; + prevEventAction = fPrevEventAction; + prevEventAction.setText(prevAction.getText()); + prevEventAction.setToolTipText(prevAction.getToolTipText()); + prevEventAction.setImageDescriptor(prevAction.getImageDescriptor()); + } + return prevEventAction; + } + + @Override + protected void fillTimeGraphEntryContextMenu(@NonNull IMenuManager menuManager) { + ISelection selection = getSite().getSelectionProvider().getSelection(); + if (selection instanceof StructuredSelection) { + StructuredSelection sSel = (StructuredSelection) selection; + if (sSel.getFirstElement() instanceof TimeGraphEntry) { + TimeGraphEntry entry = (TimeGraphEntry) sSel.getFirstElement(); + ITmfTreeDataModel entryModel = entry.getEntryModel(); + if (entryModel instanceof IElementResolver) { + Multimap<@NonNull String, @NonNull Object> metadata = ((IElementResolver) entryModel).getMetadata(); + Collection<@NonNull Object> tids = metadata.get(OsStrings.tid()); + if (tids.size() == 1) { + HostThread hostThread = new HostThread(getTrace(entry).getHostId(), (Integer) tids.iterator().next()); + menuManager.add(new FollowThreadAction(FlameChartView.this, String.valueOf(hostThread.getTid()), hostThread)); + } + } + } + } + } + + // ------------------------------------------------------------------------ + // Methods related to function name mapping + // ------------------------------------------------------------------------ + + private Action getConfigureSymbolsAction() { + if (fConfigureSymbolsAction != null) { + return fConfigureSymbolsAction; + } + fConfigureSymbolsAction = new Action(Messages.CallStackView_ConfigureSymbolProvidersText) { + @Override + public void run() { + SymbolProviderConfigDialog dialog = new SymbolProviderConfigDialog(getSite().getShell(), getProviderPages()); + if (dialog.open() == IDialogConstants.OK_ID) { + List traceEntries = getEntryList(getTrace()); + if (traceEntries != null) { + for (TraceEntry traceEntry : Iterables.filter(traceEntries, TraceEntry.class)) { + ITimeGraphDataProvider provider = traceEntry.getProvider(); + if (provider instanceof FlameChartDataProvider) { + ((FlameChartDataProvider) provider).resetFunctionNames(new NullProgressMonitor()); + } + + // reset full and zoomed events here + Iterable flatten = Utils.flatten(traceEntry); + flatten.forEach(e -> e.setSampling(null)); + + // recompute full events + long start = traceEntry.getStartTime(); + long end = traceEntry.getEndTime(); + final long resolution = Long.max(1, (end - start) / getDisplayWidth()); + zoomEntries(flatten, start, end, resolution, new NullProgressMonitor()); + } + // zoomed events will be retriggered by refreshing + refresh(); + } + synchingToTime(getTimeGraphViewer().getSelectionBegin()); + } + } + }; + Action configureSymbolsAction = fConfigureSymbolsAction; + configureSymbolsAction.setToolTipText(Messages.CallStackView_ConfigureSymbolProvidersTooltip); + configureSymbolsAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath(IMPORT_BINARY_ICON_PATH)); + + /* + * The updateConfigureSymbolsAction() method (called by refresh()) will + * set the action to true if applicable after the symbol provider has + * been properly loaded. + */ + configureSymbolsAction.setEnabled(false); + + return configureSymbolsAction; + } + + /** + * @return an array of {@link ISymbolProviderPreferencePage} that will + * configure the current traces + */ + private ISymbolProviderPreferencePage[] getProviderPages() { + List pages = new ArrayList<>(); + ITmfTrace trace = getTrace(); + if (trace != null) { + for (ITmfTrace subTrace : getTracesToBuild(trace)) { + @SuppressWarnings("null") + Collection<@NonNull ISymbolProvider> symbolProviders = SymbolProviderManager.getInstance().getSymbolProviders(subTrace); + for (org.eclipse.tracecompass.tmf.ui.symbols.ISymbolProvider provider : Iterables.filter(symbolProviders, org.eclipse.tracecompass.tmf.ui.symbols.ISymbolProvider.class)) { + ISymbolProviderPreferencePage page = provider.createPreferencePage(); + if (page != null) { + pages.add(page); + } + } + } + } + return pages.toArray(new ISymbolProviderPreferencePage[pages.size()]); + } + + /** + * Update the enable status of the configure symbols action + */ + private void updateConfigureSymbolsAction() { + ISymbolProviderPreferencePage[] providerPages = getProviderPages(); + getConfigureSymbolsAction().setEnabled(providerPages.length > 0); + } + + @TmfSignalHandler + @Override + public void traceClosed(@Nullable TmfTraceClosedSignal signal) { + if (signal == null) { + return; + } + List<@NonNull TimeGraphEntry> traceEntries = getEntryList(signal.getTrace()); + if (traceEntries != null) { + /* + * remove functions associated to the trace's entries. + */ + Iterable all = Iterables.concat(Iterables.transform(traceEntries, Utils::flatten)); + all.forEach(entry -> fFunctions.remove(entry.getEntryModel().getId())); + } + super.traceClosed(signal); + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/Messages.java b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/Messages.java new file mode 100644 index 0000000000..37b47242cd --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/Messages.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2012, 2016 Ericsson + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + *******************************************************************************/ + +package org.eclipse.tracecompass.internal.analysis.callstack.ui; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.osgi.util.NLS; + +/** + * TMF message bundle + */ +@SuppressWarnings("javadoc") +public class Messages extends NLS { + private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$ + + public static @Nullable String CallStackView_ConfigureSymbolProvidersText; + public static @Nullable String CallStackView_ConfigureSymbolProvidersTooltip; + public static @Nullable String CallStackView_DepthColumn; + public static @Nullable String CallStackView_DurationColumn; + public static @Nullable String CallStackView_EntryTimeColumn; + public static @Nullable String CallStackView_ExitTimeColumn; + public static @Nullable String CallStackView_FunctionColumn; + public static @Nullable String CallStackView_StackInfoNotAvailable; + public static @Nullable String CallStackView_ThreadColumn; + + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/messages.properties b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/messages.properties new file mode 100644 index 0000000000..e38c6d56b5 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.callstack.ui/src/org/eclipse/tracecompass/internal/analysis/callstack/ui/messages.properties @@ -0,0 +1,23 @@ +############################################################################### +# Copyright (c) 2013, 2016 Ericsson +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License 2.0 +# which accompanies this distribution, and is available at +# https://www.eclipse.org/legal/epl-2.0 +# +# SPDX-License-Identifier: EPL-2.0 +# +# Contributors: +# Ericsson - Initial API and implementation +############################################################################### + +CallStackView_ConfigureSymbolProvidersText=Configure symbol providers +CallStackView_ConfigureSymbolProvidersTooltip=Configure how the addresses are mapped to function names +CallStackView_DepthColumn=Depth +CallStackView_DurationColumn=Duration +CallStackView_EntryTimeColumn=Entry time +CallStackView_ExitTimeColumn=Exit time +CallStackView_FunctionColumn=Function +CallStackView_StackInfoNotAvailable=Stack info not available +CallStackView_ThreadColumn=Thread diff --git a/analysis/org.eclipse.tracecompass.analysis.os.linux.core/META-INF/MANIFEST.MF b/analysis/org.eclipse.tracecompass.analysis.os.linux.core/META-INF/MANIFEST.MF index cc2294aedc..9a86ff3c0f 100644 --- a/analysis/org.eclipse.tracecompass.analysis.os.linux.core/META-INF/MANIFEST.MF +++ b/analysis/org.eclipse.tracecompass.analysis.os.linux.core/META-INF/MANIFEST.MF @@ -61,5 +61,9 @@ Export-Package: org.eclipse.tracecompass.analysis.os.linux.core.contextswitch, org.eclipse.tracecompass.analysis.os.linux.ui, org.eclipse.tracecompass.lttng2.kernel.core.tests", org.eclipse.tracecompass.internal.analysis.os.linux.core.resourcesstatus;x-friends:="org.eclipse.tracecompass.analysis.os.linux.ui", - org.eclipse.tracecompass.internal.analysis.os.linux.core.threadstatus;x-friends:="org.eclipse.tracecompass.analysis.os.linux.ui,org.eclipse.tracecompass.analysis.os.linux.core.tests,org.eclipse.tracecompass.analysis.os.linux.ui.tests" + org.eclipse.tracecompass.internal.analysis.os.linux.core.threadstatus; + x-friends:="org.eclipse.tracecompass.analysis.callstack.core, + org.eclipse.tracecompass.analysis.os.linux.core.tests, + org.eclipse.tracecompass.analysis.os.linux.ui, + org.eclipse.tracecompass.analysis.os.linux.ui.tests" Automatic-Module-Name: org.eclipse.tracecompass.analysis.os.linux.core diff --git a/analysis/org.eclipse.tracecompass.analysis.os.linux.ui/META-INF/MANIFEST.MF b/analysis/org.eclipse.tracecompass.analysis.os.linux.ui/META-INF/MANIFEST.MF index a67a8fc17d..0992d7ec7a 100644 --- a/analysis/org.eclipse.tracecompass.analysis.os.linux.ui/META-INF/MANIFEST.MF +++ b/analysis/org.eclipse.tracecompass.analysis.os.linux.ui/META-INF/MANIFEST.MF @@ -26,7 +26,7 @@ Import-Package: com.google.common.annotations, com.google.common.collect, org.eclipse.swtchart Export-Package: org.eclipse.tracecompass.internal.analysis.os.linux.ui;x-friends:="org.eclipse.tracecompass.analysis.os.linux.ui.swtbot.tests", - org.eclipse.tracecompass.internal.analysis.os.linux.ui.actions;x-internal:=true, + org.eclipse.tracecompass.internal.analysis.os.linux.ui.actions;x-friends:="org.eclipse.tracecompass.analysis.callstack.ui", org.eclipse.tracecompass.internal.analysis.os.linux.ui.perspectives;x-internal:=true, org.eclipse.tracecompass.internal.analysis.os.linux.ui.views.controlflow; x-friends:="org.eclipse.tracecompass.analysis.os.linux.ui.swtbot.tests, diff --git a/tmf/org.eclipse.tracecompass.tmf.core/META-INF/MANIFEST.MF b/tmf/org.eclipse.tracecompass.tmf.core/META-INF/MANIFEST.MF index 633efe26b7..1bdce69046 100644 --- a/tmf/org.eclipse.tracecompass.tmf.core/META-INF/MANIFEST.MF +++ b/tmf/org.eclipse.tracecompass.tmf.core/META-INF/MANIFEST.MF @@ -48,7 +48,9 @@ Export-Package: org.eclipse.tracecompass.internal.provisional.tmf.core.model, org.eclipse.tracecompass.analysis.profiling.core, org.eclipse.tracecompass.tmf.core.tests", org.eclipse.tracecompass.internal.tmf.core.model.filters; - x-friends:="org.eclipse.tracecompass.analysis.counters.ui, + x-friends:="org.eclipse.tracecompass.analysis.callstack.core, + org.eclipse.tracecompass.analysis.callstack.ui, + org.eclipse.tracecompass.analysis.counters.ui, org.eclipse.tracecompass.analysis.graph.core, org.eclipse.tracecompass.analysis.graph.ui, org.eclipse.tracecompass.analysis.os.linux.core, diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/META-INF/MANIFEST.MF b/tmf/org.eclipse.tracecompass.tmf.ui/META-INF/MANIFEST.MF index b00365df91..330250645f 100644 --- a/tmf/org.eclipse.tracecompass.tmf.ui/META-INF/MANIFEST.MF +++ b/tmf/org.eclipse.tracecompass.tmf.ui/META-INF/MANIFEST.MF @@ -36,7 +36,7 @@ Require-Bundle: org.eclipse.core.expressions, Export-Package: org.eclipse.tracecompass.internal.provisional.tmf.ui.model;x-internal:=true, org.eclipse.tracecompass.internal.provisional.tmf.ui.viewers.xychart;x-internal:=true, org.eclipse.tracecompass.internal.provisional.tmf.ui.widgets;x-friends:="org.eclipse.tracecompass.analysis.timing.ui", - org.eclipse.tracecompass.internal.provisional.tmf.ui.widgets.timegraph;x-internal:=true, + org.eclipse.tracecompass.internal.provisional.tmf.ui.widgets.timegraph;x-friends:="org.eclipse.tracecompass.analysis.callstack.ui", org.eclipse.tracecompass.internal.tmf.ui;x-friends:="org.eclipse.tracecompass.tmf.ui.tests,org.eclipse.tracecompass.tmf.ctf.ui.tests", org.eclipse.tracecompass.internal.tmf.ui.commands;x-friends:="org.eclipse.tracecompass.analysis.timing.ui", org.eclipse.tracecompass.internal.tmf.ui.dialogs;x-internal:=true,