blob: 859703288079b5236f5d5c7e3be2000f190cf509 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Ericsson
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Author:
* Sonia Farrah
*******************************************************************************/
package org.eclipse.tracecompass.incubator.internal.callstack.ui.flamegraph;
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.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuCreator;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MenuDetectEvent;
import org.eclipse.swt.events.MenuDetectListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.tracecompass.common.core.NonNullUtils;
import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils.FlowScopeLog;
import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils.FlowScopeLogBuilder;
import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.AllGroupDescriptor;
import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.IWeightedTreeGroupDescriptor;
import org.eclipse.tracecompass.incubator.callstack.core.callgraph.ICallGraphProvider;
import org.eclipse.tracecompass.incubator.internal.callstack.core.flamegraph.DataProviderUtils;
import org.eclipse.tracecompass.incubator.internal.callstack.core.flamegraph.FlameGraphDataProvider;
import org.eclipse.tracecompass.incubator.internal.callstack.ui.Activator;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.TmfFilterAppliedSignal;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.filters.TraceCompassFilter;
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.statesystem.core.StateSystemUtils;
import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderManager;
import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils;
import org.eclipse.tracecompass.tmf.core.model.IOutputElement;
import org.eclipse.tracecompass.tmf.core.model.filters.SelectionTimeQueryFilter;
import org.eclipse.tracecompass.tmf.core.model.timegraph.IFilterProperty;
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.timegraph.TimeGraphState;
import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeModel;
import org.eclipse.tracecompass.tmf.core.response.ITmfResponse;
import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal;
import org.eclipse.tracecompass.tmf.core.symbols.ISymbolProvider;
import org.eclipse.tracecompass.tmf.core.symbols.SymbolProviderManager;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.eclipse.tracecompass.tmf.ui.TmfUiRefreshHandler;
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.symbols.TmfSymbolProviderUpdatedSignal;
import org.eclipse.tracecompass.tmf.ui.views.SaveImageUtil;
import org.eclipse.tracecompass.tmf.ui.views.TmfView;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphViewer;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent;
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.NullTimeEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry.Sampling;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphControl;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.Utils.TimeFormat;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
/**
* View to display the flame graph .This uses the flameGraphNode tree generated
* by CallGraphAnalysisUI.
*
* @author Sonia Farrah
*/
@NonNullByDefault({})
public class FlameGraphView extends TmfView {
/**
* ID of the view
*/
public static final String ID = FlameGraphView.class.getPackage().getName() + ".flamegraphView"; //$NON-NLS-1$
private static final @NonNull String SYMBOL_MAPPING_ICON_PATH = "icons/obj16/binaries_obj.gif"; //$NON-NLS-1$
private static final @NonNull String GROUP_BY_ICON_PATH = "icons/etool16/group_by.gif"; //$NON-NLS-1$
private static final String SORT_OPTION_KEY = "sort.option"; //$NON-NLS-1$
private static final ImageDescriptor SORT_BY_NAME_ICON = Activator.getDefault().getImageDescripterFromPath("icons/etool16/sort_alpha.gif"); //$NON-NLS-1$
private static final ImageDescriptor SORT_BY_NAME_REV_ICON = Activator.getDefault().getImageDescripterFromPath("icons/etool16/sort_alpha_rev.gif"); //$NON-NLS-1$
private static final ImageDescriptor SORT_BY_ID_ICON = Activator.getDefault().getImageDescripterFromPath("icons/etool16/sort_num.gif"); //$NON-NLS-1$
private static final ImageDescriptor SORT_BY_ID_REV_ICON = Activator.getDefault().getImageDescripterFromPath("icons/etool16/sort_num_rev.gif"); //$NON-NLS-1$
private static final ImageDescriptor AGGREGATE_BY_ICON = Activator.getDefault().getImageDescripterFromPath(GROUP_BY_ICON_PATH);
private static final int DEFAULT_BUFFER_SIZE = 3;
private TimeGraphViewer fTimeGraphViewer;
private SortOption fSortOption = SortOption.BY_NAME;
private BaseDataProviderTimeGraphPresentationProvider fPresentationProvider;
private ITmfTrace fTrace;
private static final @NonNull Logger LOGGER = Logger.getLogger(FlameGraphView.class.getName());
private final @NonNull MenuManager fEventMenuManager = new MenuManager();
private Action fAggregateByAction;
private Action fSortByNameAction;
private Action fSortByIdAction;
// The action to import a binary file mapping */
private Action fConfigureSymbolsAction;
private @Nullable IWeightedTreeGroupDescriptor fGroupBy = null;
/**
* A plain old semaphore is used since different threads will be competing
* for the same resource.
*/
private final Semaphore fLock = new Semaphore(1);
// Variable used to specify when the graph is dirty, ie waiting for data refresh
private final AtomicInteger fDirty = new AtomicInteger();
/** The trace to build thread hash map */
private final Map<ITmfTrace, Job> fBuildJobMap = new HashMap<>();
private final Map<ITimeGraphDataProvider<? extends @NonNull TimeGraphEntryModel>, Map<Long, @NonNull TimeGraphEntry>> fEntries = new HashMap<>();
/**
* Set of visible entries to zoom on.
*/
private @NonNull Set<@NonNull TimeGraphEntry> fVisibleEntries = Collections.emptySet();
private long fEndTime = Long.MIN_VALUE;
/** The trace to entry list hash map */
private final Map<ITmfTrace, List<@NonNull TimeGraphEntry>> fEntryListMap = new HashMap<>();
private int fDisplayWidth;
private @Nullable ZoomThread fZoomThread;
private final Object fZoomThreadResultLock = new Object();
/**
* Constructor
*/
public FlameGraphView() {
super(ID);
}
/**
* Constructor with ID
*
* @param id
* The ID of the view
*/
protected FlameGraphView(String id) {
super(id);
}
@Override
public void createPartControl(Composite parent) {
super.createPartControl(parent);
fDisplayWidth = Display.getDefault().getBounds().width;
fTimeGraphViewer = new TimeGraphViewer(parent, SWT.NONE);
fPresentationProvider = new BaseDataProviderTimeGraphPresentationProvider();
fTimeGraphViewer.setTimeGraphProvider(fPresentationProvider);
fTimeGraphViewer.setTimeFormat(TimeFormat.NUMBER);
IEditorPart editor = getSite().getPage().getActiveEditor();
ITmfTrace trace = null;
if (editor instanceof ITmfTraceEditor) {
trace = ((ITmfTraceEditor) editor).getTrace();
} else {
// Get the active trace, the editor might be opened on a script
trace = TmfTraceManager.getInstance().getActiveTrace();
}
if (trace != null) {
traceSelected(new TmfTraceSelectedSignal(this, trace));
}
contributeToActionBars();
loadSortOption();
TmfSignalManager.register(this);
getSite().setSelectionProvider(fTimeGraphViewer.getSelectionProvider());
createTimeEventContextMenu();
fTimeGraphViewer.getTimeGraphControl().addMouseListener(new MouseAdapter() {
@Override
public void mouseDoubleClick(MouseEvent e) {
TimeGraphControl timeGraphControl = getTimeGraphViewer().getTimeGraphControl();
ISelection selection = timeGraphControl.getSelection();
if (selection instanceof IStructuredSelection) {
for (Object object : ((IStructuredSelection) selection).toList()) {
if (object instanceof TimeEvent) {
TimeEvent event = (TimeEvent) object;
long startTime = event.getTime();
long endTime = startTime + event.getDuration();
getTimeGraphViewer().setStartFinishTime(startTime, endTime);
break;
}
}
}
}
});
fTimeGraphViewer.addRangeListener(event -> {
startZoomThread(event.getStartTime(), event.getEndTime(), false);
});
TimeGraphControl timeGraphControl = fTimeGraphViewer.getTimeGraphControl();
timeGraphControl.addPaintListener(new PaintListener() {
/**
* This paint control allows the virtual time graph refresh to occur on paint
* events instead of just scrolling the time axis or zooming. To avoid
* refreshing the model on every paint event, we use a TmfUiRefreshHandler to
* coalesce requests and only execute the last one, we also check if the entries
* have changed to avoid useless model refresh.
*
* @param e
* paint event on the visible area
*/
@Override
public void paintControl(PaintEvent e) {
TmfUiRefreshHandler.getInstance().queueUpdate(this, () -> {
if (timeGraphControl.isDisposed()) {
return;
}
Set<@NonNull TimeGraphEntry> newSet = getVisibleItems(DEFAULT_BUFFER_SIZE);
if (!fVisibleEntries.equals(newSet)) {
/*
* Start a zoom thread if the set of visible entries has changed. We do not use
* lists as the order is not important. We cannot use the start index / size of
* the visible entries as we can collapse / reorder events.
*/
fVisibleEntries = newSet;
startZoomThread(getTimeGraphViewer().getTime0(), getTimeGraphViewer().getTime1(), false);
}
});
}
});
}
/**
* Get the time graph viewer
*
* @return the time graph viewer
*/
@VisibleForTesting
public TimeGraphViewer getTimeGraphViewer() {
return fTimeGraphViewer;
}
/**
* Handler for the trace selected signal
*
* @param signal
* The incoming signal
*/
@TmfSignalHandler
public void traceSelected(final TmfTraceSelectedSignal signal) {
ITmfTrace trace = signal.getTrace();
fTrace = trace;
if (trace == null) {
return;
}
// If entries for this trace are already available, just zoom on them,
// otherwise, rebuild
List<@NonNull TimeGraphEntry> list = fEntryListMap.get(trace);
if (list == null) {
refresh();
Display.getDefault().asyncExec(() -> buildFlameGraph(trace, null, null));
} else {
// Reset end time
long endTime = Long.MIN_VALUE;
for (TimeGraphEntry entry : list) {
endTime = Math.max(endTime, entry.getEndTime());
}
setEndTime(endTime);
refresh();
startZoomThread(0, endTime, false);
}
}
/**
* Get the callgraph modules used to build the view
*
* @return The call graph provider modules
*/
protected Iterable<ICallGraphProvider> getCallgraphModules() {
ITmfTrace trace = fTrace;
if (trace == null) {
return null;
}
String analysisId = NonNullUtils.nullToEmptyString(getViewSite().getSecondaryId());
Iterable<ICallGraphProvider> modules = TmfTraceUtils.getAnalysisModulesOfClass(trace, ICallGraphProvider.class);
return StreamSupport.stream(modules.spliterator(), false)
.filter(m -> {
if (m instanceof IAnalysisModule) {
return ((IAnalysisModule) m).getId().equals(analysisId);
}
return true;
})
.collect(Collectors.toSet());
}
private String getProviderId() {
String secondaryId = this.getViewSite().getSecondaryId();
// The secondary ID may contain the '[COLON]' text, in which case, it
// should be replace with a real ':' and this is the complete
// providerId. This kind of secondary ID may come from external sources
// of data provider, such as scripting
return (secondaryId == null) ? FlameGraphDataProvider.ID : (secondaryId.contains("[COLON]")) ? secondaryId.replace("[COLON]", ":") : FlameGraphDataProvider.ID + ':' + secondaryId;
}
private class BuildRunnable {
private final @NonNull ITmfTrace fBuildTrace;
private final @NonNull ITmfTrace fParentTrace;
private final @NonNull FlowScopeLog fScope;
private final @NonNull Map<String, Object> fParameters;
public BuildRunnable(final @NonNull ITmfTrace trace, final @NonNull ITmfTrace parentTrace, @Nullable ITmfTimestamp selStart, @Nullable ITmfTimestamp selEnd, final @NonNull FlowScopeLog log) {
fBuildTrace = trace;
fParentTrace = parentTrace;
fScope = log;
if (selStart != null && selEnd != null) {
fParameters = ImmutableMap.of(FlameGraphDataProvider.SELECTION_RANGE_KEY, ImmutableList.of(selStart.toNanos(), selEnd.toNanos()));
} else {
fParameters = Collections.emptyMap();
}
}
public void run(IProgressMonitor monitor) {
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameGraphView:BuildThread", "trace", fBuildTrace.getName()).setParentScope(fScope).build()) { //$NON-NLS-1$ //$NON-NLS-2$
buildEntryList(fBuildTrace, fParentTrace, fParameters, NonNullUtils.checkNotNull(monitor));
synchronized (fBuildJobMap) {
fBuildJobMap.remove(fBuildTrace);
}
}
}
}
private void buildEntryList(@NonNull ITmfTrace trace, @NonNull ITmfTrace parentTrace, @NonNull Map<String, Object> additionalParams, @NonNull IProgressMonitor monitor) {
ITimeGraphDataProvider<@NonNull TimeGraphEntryModel> dataProvider = DataProviderManager
.getInstance().getDataProvider(trace, getProviderId(), ITimeGraphDataProvider.class);
if (dataProvider == null) {
return;
}
BaseDataProviderTimeGraphPresentationProvider presentationProvider = fPresentationProvider;
if (presentationProvider != null) {
presentationProvider.addProvider(dataProvider, getTooltipResolver(dataProvider));
}
boolean complete = false;
while (!complete && !monitor.isCanceled()) {
Map<String, Object> parameters = new HashMap<>(additionalParams);
parameters.put(DataProviderParameterUtils.REQUESTED_TIME_KEY, ImmutableList.of(0, Long.MAX_VALUE));
IWeightedTreeGroupDescriptor groupBy = fGroupBy;
if (groupBy != null) {
parameters.put(FlameGraphDataProvider.GROUP_BY_KEY, groupBy.getName());
}
TmfModelResponse<TmfTreeModel<@NonNull TimeGraphEntryModel>> response = dataProvider.fetchTree(parameters, monitor);
if (response.getStatus() == ITmfResponse.Status.FAILED) {
Activator.getDefault().logError(getClass().getSimpleName() + " Data Provider failed: " + response.getStatusMessage()); //$NON-NLS-1$
return;
} else if (response.getStatus() == ITmfResponse.Status.CANCELLED) {
return;
}
complete = response.getStatus() == ITmfResponse.Status.COMPLETED;
TmfTreeModel<@NonNull TimeGraphEntryModel> model = response.getModel();
long endTime = Long.MIN_VALUE;
if (model != null) {
Map<Long, TimeGraphEntry> entries;
synchronized (fEntries) {
entries = fEntries.computeIfAbsent(dataProvider, dp -> new HashMap<>());
/*
* The provider may send entries unordered and parents may
* not exist when child is constructor, we'll re-unite
* families at the end
*/
List<TimeGraphEntry> orphaned = new ArrayList<>();
for (TimeGraphEntryModel entry : model.getEntries()) {
TimeGraphEntry uiEntry = entries.get(entry.getId());
if (entry.getParentId() != -1) {
if (uiEntry == null) {
uiEntry = new TimeGraphEntry(entry);
TimeGraphEntry parent = entries.get(entry.getParentId());
if (parent != null) {
parent.addChild(uiEntry);
} else {
orphaned.add(uiEntry);
}
entries.put(entry.getId(), uiEntry);
} else {
uiEntry.updateModel(entry);
}
} else {
endTime = Long.max(endTime, entry.getEndTime() + 1);
if (uiEntry != null) {
uiEntry.updateModel(entry);
} else {
// Do not assume that parentless entries are
// trace entries
uiEntry = new ParentEntry(entry, dataProvider);
entries.put(entry.getId(), uiEntry);
addToEntryList(parentTrace, Collections.singletonList(uiEntry));
}
}
}
setEndTime(endTime);
// Find missing parents
for (TimeGraphEntry orphanedEntry : orphaned) {
TimeGraphEntry parent = entries.get(orphanedEntry.getEntryModel().getParentId());
if (parent != null) {
parent.addChild(orphanedEntry);
}
}
}
long start = 0;
long end = getEndTime();
final long resolution = Long.max(1, (end - start) / getDisplayWidth());
zoomEntries(ImmutableList.copyOf(entries.values()), start, end, resolution, monitor);
}
if (monitor.isCanceled()) {
return;
}
if (parentTrace.equals(getTrace())) {
refresh();
}
monitor.worked(1);
if (!complete && !monitor.isCanceled()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Activator.getDefault().logError("Failed to wait for data provider", e); //$NON-NLS-1$
}
}
}
}
/**
* Get the presentation provider
*
* @return the presentation provider
*/
@VisibleForTesting
public BaseDataProviderTimeGraphPresentationProvider getPresentationProvider() {
return fPresentationProvider;
}
private static BiFunction<ITimeEvent, Long, Map<String, String>> getTooltipResolver(ITimeGraphDataProvider<? extends TimeGraphEntryModel> provider) {
return (event, time) -> {
return getTooltip(event, time, provider, false);
};
}
private static Map<String, String> getTooltip(ITimeEvent event, Long time, ITimeGraphDataProvider<? extends TimeGraphEntryModel> provider, boolean getActions) {
ITimeGraphEntry entry = event.getEntry();
if (!(entry instanceof TimeGraphEntry)) {
return Collections.emptyMap();
}
long entryId = ((TimeGraphEntry) entry).getEntryModel().getId();
IOutputElement element = null;
if (event instanceof TimeEvent) {
element = ((TimeEvent) event).getModel();
}
Map<@NonNull String, @NonNull Object> parameters = getFetchTooltipParameters(time, entryId, element);
if (getActions) {
parameters.put(FlameGraphDataProvider.TOOLTIP_ACTION_KEY, true);
}
TmfModelResponse<Map<String, String>> response = provider.fetchTooltip(parameters, new NullProgressMonitor());
Map<String, String> tooltip = response.getModel();
return (tooltip == null) ? Collections.emptyMap() : tooltip;
}
private static Map<String, Object> getFetchTooltipParameters(long time, long item, @Nullable IOutputElement element) {
@NonNull Map<String, Object> parameters = new HashMap<>();
parameters.put(DataProviderParameterUtils.REQUESTED_TIME_KEY, Collections.singletonList(time));
parameters.put(DataProviderParameterUtils.REQUESTED_ITEMS_KEY, Collections.singletonList(item));
if (element != null) {
parameters.put(DataProviderParameterUtils.REQUESTED_ELEMENT_KEY, element);
}
return parameters;
}
/**
* Zoom thread
*
* @since 1.1
*/
protected class ZoomThread extends Thread {
private final long fZoomStartTime;
private final long fZoomEndTime;
private final long fResolution;
private int fScopeId = -1;
private final @NonNull IProgressMonitor fMonitor;
private @NonNull Collection<@NonNull TimeGraphEntry> fCurrentEntries;
private boolean fForce;
/**
* Constructor
*
* @param entries
* The entries to zoom on
* @param startTime
* the start time
* @param endTime
* the end time
* @param resolution
* the resolution
* @param force
* Whether to force the zoom of all entries or only those
* that have not the same sampling
*/
public ZoomThread(@NonNull Collection<@NonNull TimeGraphEntry> entries, long startTime, long endTime, long resolution, boolean force) {
super(FlameGraphView.this.getName() + " zoom"); //$NON-NLS-1$
fZoomStartTime = startTime;
fZoomEndTime = endTime;
fResolution = resolution;
fCurrentEntries = entries;
fMonitor = new NullProgressMonitor();
fForce = force;
}
/**
* @return the zoom start time
*/
public long getZoomStartTime() {
return fZoomStartTime;
}
/**
* @return the zoom end time
*/
public long getZoomEndTime() {
return fZoomEndTime;
}
/**
* @return the resolution
*/
public long getResolution() {
return fResolution;
}
/**
* @return the monitor
*/
public @NonNull IProgressMonitor getMonitor() {
return fMonitor;
}
/**
* Cancel the zoom thread
*/
public void cancel() {
fMonitor.setCanceled(true);
}
@Override
public final void run() {
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameGraphView:ZoomThread", "start", fZoomStartTime, "end", fZoomEndTime).setCategoryAndId(getViewId(), fScopeId).build()) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (fCurrentEntries.isEmpty()) {
// No rows to zoom on
return;
}
Sampling sampling = new Sampling(getZoomStartTime(), getZoomEndTime(), getResolution());
Iterable<@NonNull TimeGraphEntry> incorrectSample = fForce ? fCurrentEntries : Iterables.filter(fCurrentEntries, entry -> !sampling.equals(entry.getSampling()));
zoomEntries(incorrectSample, getZoomStartTime(), getZoomEndTime(), getResolution(), getMonitor());
} finally {
if (fDirty.decrementAndGet() < 0) {
Activator.getDefault().logError("Dirty underflow", new Throwable()); //$NON-NLS-1$
}
}
}
/**
* Set the ID of the calling flow scope. This data will allow to determine the
* causality between the zoom thread and its caller if tracing is enabled.
*
* @param scopeId
* The ID of the calling flow scope
* @since 3.0
*/
public void setScopeId(int scopeId) {
fScopeId = scopeId;
}
}
/**
* Start or restart the zoom thread.
*
* @param startTime
* the zoom start time
* @param endTime
* the zoom end time
* @param force
* Whether to force the fetch of all rows, or only those that
* don't have the same range
*/
protected final void startZoomThread(long startTime, long endTime, boolean force) {
ITmfTrace trace = getTrace();
if (trace == null) {
return;
}
fDirty.incrementAndGet();
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameGraphView:ZoomThreadCreated").setCategory(getViewId()).build()) { //$NON-NLS-1$
long clampedStartTime = Math.max(0, Math.min(startTime, getEndTime()));
long clampedEndTime = Math.min(getEndTime(), Math.max(endTime, 0));
// Ignore if end time < start time, data has not been set correctly [yet]
if (clampedEndTime < clampedStartTime) {
return;
}
ZoomThread zoomThread = fZoomThread;
if (zoomThread != null) {
zoomThread.cancel();
}
int timeSpace = getTimeGraphViewer().getTimeSpace();
if (timeSpace > 0) {
long resolution = Long.max(1, (clampedEndTime - clampedStartTime) / timeSpace);
zoomThread = new ZoomThread(getVisibleItems(DEFAULT_BUFFER_SIZE), clampedStartTime, clampedEndTime, resolution, force);
} else {
zoomThread = null;
}
fZoomThread = zoomThread;
if (zoomThread != null) {
zoomThread.setScopeId(log.getId());
/*
* Don't start a new thread right away if results are being applied from an old
* ZoomThread. Otherwise, the old results might overwrite the new results if it
* finishes after.
*/
synchronized (fZoomThreadResultLock) {
zoomThread.start();
// zoomThread decrements, so we increment here
fDirty.incrementAndGet();
}
}
} finally {
if (fDirty.decrementAndGet() < 0) {
Activator.getDefault().logError("Dirty underflow", new Throwable()); //$NON-NLS-1$
}
}
}
private @NonNull Set<@NonNull TimeGraphEntry> getVisibleItems(int buffer) {
TimeGraphControl timeGraphControl = fTimeGraphViewer.getTimeGraphControl();
if (timeGraphControl.isDisposed()) {
return Collections.emptySet();
}
int start = Integer.max(0, fTimeGraphViewer.getTopIndex() - buffer);
int end = Integer.min(fTimeGraphViewer.getExpandedElementCount() - 1,
fTimeGraphViewer.getTopIndex() + timeGraphControl.countPerPage() + buffer);
Set<@NonNull TimeGraphEntry> visible = new HashSet<>(end - start + 1);
for (int i = start; i <= end; i++) {
/*
* Use the getExpandedElement by index to avoid creating a copy of
* all the the elements.
*/
TimeGraphEntry element = (TimeGraphEntry) timeGraphControl.getExpandedElement(i);
if (element != null) {
visible.add(element);
}
}
return visible;
}
private void zoomEntries(@NonNull Iterable<@NonNull TimeGraphEntry> entries, long zoomStartTime, long zoomEndTime, long resolution, @NonNull IProgressMonitor monitor) {
if (resolution < 0) {
// StateSystemUtils.getTimes would throw an illegal argument exception.
return;
}
long start = Long.min(zoomStartTime, zoomEndTime);
long end = Long.max(zoomStartTime, zoomEndTime);
List<@NonNull Long> times = StateSystemUtils.getTimes(start, end, resolution);
Sampling sampling = new Sampling(start, end, resolution);
Multimap<ITimeGraphDataProvider<? extends TimeGraphEntryModel>, Long> providersToModelIds = filterGroupEntries(entries, zoomStartTime, zoomEndTime);
SubMonitor subMonitor = SubMonitor.convert(monitor, getClass().getSimpleName() + "#zoomEntries", providersToModelIds.size()); //$NON-NLS-1$
for (Entry<ITimeGraphDataProvider<? extends TimeGraphEntryModel>, Collection<Long>> entry : providersToModelIds.asMap().entrySet()) {
ITimeGraphDataProvider<? extends TimeGraphEntryModel> dataProvider = entry.getKey();
SelectionTimeQueryFilter filter = new SelectionTimeQueryFilter(times, entry.getValue());
Map<@NonNull String, @NonNull Object> parameters = FetchParametersUtils.selectionTimeQueryToMap(filter);
Multimap<@NonNull Integer, @NonNull String> regexesMap = getRegexes();
if (!regexesMap.isEmpty()) {
parameters.put(DataProviderParameterUtils.REGEX_MAP_FILTERS_KEY, regexesMap.asMap());
}
TmfModelResponse<TimeGraphModel> response = dataProvider.fetchRowModel(parameters, monitor);
TimeGraphModel model = response.getModel();
if (model != null) {
zoomEntries(fEntries.get(dataProvider), model.getRows(), response.getStatus() == ITmfResponse.Status.COMPLETED, sampling);
}
subMonitor.worked(1);
}
redraw();
}
/**
* This method build the multimap of regexes by property that will be used to
* filter the timegraph states
*
* Override this method to add other regexes with their properties. The data
* provider should handle everything after.
*
* @return The multimap of regexes by property
*/
private @NonNull Multimap<@NonNull Integer, @NonNull String> getRegexes() {
Multimap<@NonNull Integer, @NonNull String> regexes = HashMultimap.create();
ITmfTrace trace = getTrace();
if (trace == null) {
return regexes;
}
TraceCompassFilter globalFilter = TraceCompassFilter.getFilterForTrace(trace);
if (globalFilter == null) {
return regexes;
}
regexes.putAll(IFilterProperty.DIMMED, globalFilter.getRegexes());
return regexes;
}
private void zoomEntries(Map<Long, TimeGraphEntry> map, List<ITimeGraphRowModel> model, boolean completed, Sampling sampling) {
boolean isZoomThread = false; // Thread.currentThread() instanceof ZoomThread;
for (ITimeGraphRowModel rowModel : model) {
TimeGraphEntry entry = map.get(rowModel.getEntryID());
if (entry != null) {
List<ITimeEvent> events = createTimeEvents(entry, rowModel.getStates());
if (isZoomThread) {
synchronized (fZoomThreadResultLock) {
Display.getDefault().asyncExec(() -> {
entry.setZoomedEventList(events);
if (completed) {
entry.setSampling(sampling);
}
});
}
} else {
entry.setEventList(events);
}
}
}
}
/**
* Create {@link ITimeEvent}s for an entry from the list of
* {@link ITimeGraphState}s, filling in the gaps.
*
* @param entry
* the {@link TimeGraphEntry} on which we are working
* @param values
* the list of {@link ITimeGraphState}s from the
* {@link ITimeGraphDataProvider}.
* @return a contiguous List of {@link ITimeEvent}s
*/
private List<ITimeEvent> createTimeEvents(TimeGraphEntry entry, List<ITimeGraphState> values) {
List<ITimeEvent> events = new ArrayList<>(values.size());
ITimeEvent prev = null;
for (ITimeGraphState state : values) {
ITimeEvent event = createTimeEvent(entry, state);
if (prev != null) {
long prevEnd = prev.getTime() + prev.getDuration();
if (prevEnd < event.getTime()) {
// fill in the gap.
TimeEvent timeEvent = new TimeEvent(entry, prevEnd, event.getTime() - prevEnd);
events.add(timeEvent);
}
}
prev = event;
events.add(event);
}
return events;
}
/**
* Create a {@link TimeEvent} for a {@link TimeGraphEntry} and a
* {@link TimeGraphState}
*
* @param entry
* {@link TimeGraphEntry} for which we create a state
* @param state
* {@link ITimeGraphState} from the data provider
* @return a new {@link TimeEvent} for these arguments
*/
protected TimeEvent createTimeEvent(TimeGraphEntry entry, ITimeGraphState state) {
String label = state.getLabel();
if (state.getValue() == Integer.MIN_VALUE && label == null && state.getStyle() == null) {
return new NullTimeEvent(entry, state.getStartTime(), state.getDuration());
}
if (label != null) {
return new NamedTimeEvent(entry, label, state);
}
return new TimeEvent(entry, state);
}
/**
* Filter the entries to return only the Non Null {@link TimeGraphEntry} which
* intersect the time range.
*
* @param visible
* the input list of visible entries
* @param zoomStartTime
* the leftmost time bound of the view
* @param zoomEndTime
* the rightmost time bound of the view
* @return A Multimap of data providers to their visible entries' model IDs.
*/
private static Multimap<ITimeGraphDataProvider<? extends TimeGraphEntryModel>, Long> filterGroupEntries(Iterable<TimeGraphEntry> visible,
long zoomStartTime, long zoomEndTime) {
Multimap<ITimeGraphDataProvider<? extends TimeGraphEntryModel>, Long> providersToModelIds = HashMultimap.create();
for (TimeGraphEntry entry : visible) {
if (zoomStartTime <= entry.getEndTime() && zoomEndTime >= entry.getStartTime() && entry.hasTimeEvents()) {
ITimeGraphDataProvider<? extends TimeGraphEntryModel> provider = getProvider(entry);
if (provider != null) {
providersToModelIds.put(provider, entry.getEntryModel().getId());
}
}
}
return providersToModelIds;
}
/**
* Get the {@link ITimeGraphDataProvider} from a {@link TimeGraphEntry}'s
* parent.
*
* @param entry
* queried {@link TimeGraphEntry}.
* @return the {@link ITimeGraphDataProvider}
* @since 3.3
*/
public static ITimeGraphDataProvider<? extends TimeGraphEntryModel> getProvider(ITimeGraphEntry entry) {
ITimeGraphEntry parent = entry;
while (parent != null) {
if (parent instanceof ParentEntry) {
return ((ParentEntry) parent).getProvider();
}
parent = parent.getParent();
}
throw new IllegalStateException(entry + " should have a TraceEntry parent"); //$NON-NLS-1$
}
/**
* Get the trace associated with this view
*
* @return The trace
*/
protected ITmfTrace getTrace() {
return fTrace;
}
private void refresh() {
try (FlowScopeLog parentLogger = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameGraphView:RefreshRequested").setCategory(getViewId()).build()) { //$NON-NLS-1$
final boolean isZoomThread = Thread.currentThread() instanceof ZoomThread;
TmfUiRefreshHandler.getInstance().queueUpdate(this, () -> {
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameGraphView:Refresh").setParentScope(parentLogger).build()) { //$NON-NLS-1$
fDirty.incrementAndGet();
if (fTimeGraphViewer.getControl().isDisposed()) {
return;
}
List<TimeGraphEntry> entries;
synchronized (fEntryListMap) {
entries = fEntryListMap.get(getTrace());
Comparator<ITimeGraphEntry> entryComparator = getEntryComparator();
if (entries == null) {
entries = new CopyOnWriteArrayList<>();
} else if (entryComparator != null) {
List<TimeGraphEntry> list = new ArrayList<>(entries);
Collections.sort(list, entryComparator);
for (ITimeGraphEntry entry : list) {
sortChildren(entry, entryComparator);
}
entries.clear();
entries.addAll(list);
}
}
boolean inputChanged = entries != fTimeGraphViewer.getInput();
if (inputChanged) {
fTimeGraphViewer.setInput(entries);
} else {
fTimeGraphViewer.refresh();
}
long startBound = 0;
long endBound = getEndTime();
endBound = (endBound == Long.MIN_VALUE ? SWT.DEFAULT : endBound);
fTimeGraphViewer.setTimeBounds(startBound, endBound);
if (inputChanged && !isZoomThread) {
fTimeGraphViewer.resetStartFinishTime();
}
} finally {
if (fDirty.decrementAndGet() < 0) {
Activator.getDefault().logError("Dirty underflow", new Throwable()); //$NON-NLS-1$
}
}
});
}
}
private Comparator<ITimeGraphEntry> getEntryComparator() {
switch (fSortOption) {
case BY_ID:
return ThreadIdComparator.getInstance();
case BY_ID_REV:
return ThreadIdComparator.getInstance().reversed();
case BY_NAME:
return ThreadNameComparator.getInstance();
case BY_NAME_REV:
return ThreadNameComparator.getInstance().reversed();
default:
break;
}
return null;
}
private void redraw() {
try (FlowScopeLog flowParent = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameGraphView:RedrawRequested").setCategory(getViewId()).build()) { //$NON-NLS-1$
Display.getDefault().asyncExec(() -> {
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameGraphView:Redraw").setParentScope(flowParent).build()) { //$NON-NLS-1$
if (fTimeGraphViewer.getControl().isDisposed()) {
return;
}
fTimeGraphViewer.getControl().redraw();
fTimeGraphViewer.getControl().update();
}
});
}
}
private void sortChildren(ITimeGraphEntry entry, Comparator<ITimeGraphEntry> comparator) {
if (entry instanceof TimeGraphEntry) {
((TimeGraphEntry) entry).sortChildren(comparator);
}
for (ITimeGraphEntry child : entry.getChildren()) {
sortChildren(child, comparator);
}
}
private int getDisplayWidth() {
int displayWidth = fDisplayWidth;
return displayWidth <= 0 ? 1 : displayWidth;
}
/**
* A class for parent entries that contain a link to the data provider
*
* @author Geneviève Bastien
*/
private static class ParentEntry extends TimeGraphEntry {
private final @NonNull ITimeGraphDataProvider<? extends TimeGraphEntryModel> fProvider;
/**
* Constructor
*
* @param model
* trace level model
* @param provider
* reference to the provider for this trace and view
*/
public ParentEntry(@NonNull TimeGraphEntryModel model,
@NonNull ITimeGraphDataProvider<? extends TimeGraphEntryModel> provider) {
super(model);
fProvider = provider;
}
/**
* Getter for the data provider for this {@link ParentEntry}
*
* @return this entry's {@link ITimeGraphDataProvider}
*/
public @NonNull ITimeGraphDataProvider<? extends TimeGraphEntryModel> getProvider() {
return fProvider;
}
}
private synchronized void setEndTime(long endTime) {
fEndTime = endTime;
}
private long getEndTime() {
return fEndTime;
}
/**
* Adds a list of entries to a trace's entry list
*
* @param trace
* the trace
* @param list
* the list of time graph entries to add
*/
private void addToEntryList(ITmfTrace trace, List<@NonNull TimeGraphEntry> list) {
synchronized (fEntryListMap) {
List<TimeGraphEntry> entryList = fEntryListMap.get(trace);
if (entryList == null) {
fEntryListMap.put(trace, new CopyOnWriteArrayList<>(list));
} else {
for (TimeGraphEntry entry : list) {
if (!entryList.contains(entry)) {
entryList.add(entry);
}
}
}
}
}
private void resetEntries(ITmfTrace trace) {
synchronized (fEntries) {
synchronized (fEntryListMap) {
// Remove the entries from the entry list map and from the
// fEntries cache
List<@NonNull TimeGraphEntry> entries = fEntryListMap.remove(trace);
if (entries == null) {
return;
}
for (TimeGraphEntry entry : entries) {
if (entry instanceof ParentEntry) {
fEntries.remove(((ParentEntry) entry).getProvider());
}
}
refresh();
}
}
}
/**
* Get the necessary data for the flame graph and display it
*
* @param viewTrace
* the trace
* @param selStart
* The selection start timestamp or <code>null</code> to show all
* data
* @param selEnd
* The selection end timestamp or <code>null</code> to show all
* data
*/
@VisibleForTesting
public void buildFlameGraph(@NonNull ITmfTrace viewTrace, @Nullable ITmfTimestamp selStart, @Nullable ITmfTimestamp selEnd) {
/*
* Note for synchronization:
*
* Acquire the lock at entry. then we have 4 places to release it
*
* 1- if the lock failed
*
* 2- if the data is null and we have no UI to update
*
* 3- when the job starts running and can thus be canceled
*/
try (FlowScopeLog log = new FlowScopeLogBuilder(LOGGER, Level.FINE, "FlameGraphView:Building").setCategory(getViewId()).build()) { //$NON-NLS-1$
try {
fLock.acquire();
} catch (InterruptedException e) {
Activator.getDefault().logError(e.getMessage(), e);
fLock.release();
}
// Run the build jobs through the site progress service if available
IWorkbenchSiteProgressService service = null;
IWorkbenchPartSite site = getSite();
if (site != null) {
service = site.getService(IWorkbenchSiteProgressService.class);
}
for (ITmfTrace trace : TmfTraceManager.getTraceSet(viewTrace)) {
// Cancel previous build job for this trace
Job buildJob = fBuildJobMap.remove(trace);
if (buildJob != null) {
buildJob.cancel();
}
resetEntries(viewTrace);
// Build job will decrement
buildJob = new Job(getTitle() + Messages.FlameGraphView_RetrievingData) {
@Override
protected IStatus run(IProgressMonitor monitor) {
new BuildRunnable(trace, viewTrace, selStart, selEnd, log).run(monitor);
monitor.done();
return Status.OK_STATUS;
}
};
fBuildJobMap.put(trace, buildJob);
if (service != null) {
service.schedule(buildJob);
} else {
buildJob.schedule();
}
}
fLock.release();
}
}
/**
* Await the next refresh
*
* @return Whether the view is ready with new data
*
* @throws InterruptedException
* something took too long
*/
@VisibleForTesting
public boolean isDirty() throws InterruptedException {
/*
* wait for the semaphore to be available, then release it immediately
* and verify dirtiness
*/
fLock.acquire();
fLock.release();
return (fDirty.get() != 0);
}
/**
* Trace is closed: clear the data structures and the view
*
* @param signal
* the signal received
*/
@TmfSignalHandler
public void traceClosed(final TmfTraceClosedSignal signal) {
if (signal.getTrace() == fTrace) {
fTimeGraphViewer.setInput(null);
}
}
@Override
public void setFocus() {
fTimeGraphViewer.setFocus();
}
// ------------------------------------------------------------------------
// Helper methods
// ------------------------------------------------------------------------
private void createTimeEventContextMenu() {
fEventMenuManager.setRemoveAllWhenShown(true);
TimeGraphControl timeGraphControl = fTimeGraphViewer.getTimeGraphControl();
final Menu timeEventMenu = fEventMenuManager.createContextMenu(timeGraphControl);
timeGraphControl.addTimeGraphEntryMenuListener(new MenuDetectListener() {
@Override
public void menuDetected(MenuDetectEvent event) {
/*
* The TimeGraphControl will call the TimeGraphEntryMenuListener
* before the TimeEventMenuListener. We need to clear the menu
* for the case the selection was done on the namespace where
* the time event listener below won't be called afterwards.
*/
timeGraphControl.setMenu(null);
event.doit = false;
}
});
timeGraphControl.addTimeEventMenuListener(new MenuDetectListener() {
@Override
public void menuDetected(MenuDetectEvent event) {
Menu menu = timeEventMenu;
if (event.data instanceof TimeEvent && !(event.data instanceof NullTimeEvent)) {
timeGraphControl.setMenu(menu);
return;
}
timeGraphControl.setMenu(null);
event.doit = false;
}
});
fEventMenuManager.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager manager) {
fillTimeEventContextMenu(fEventMenuManager);
fEventMenuManager.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
}
});
getSite().registerContextMenu(fEventMenuManager, fTimeGraphViewer.getSelectionProvider());
}
/**
* Fill context menu
*
* @param menuManager
* a menuManager to fill
*/
protected void fillTimeEventContextMenu(@NonNull IMenuManager menuManager) {
ISelection selection = getSite().getSelectionProvider().getSelection();
if (selection instanceof IStructuredSelection) {
for (Object object : ((IStructuredSelection) selection).toList()) {
if (object instanceof ITimeEvent) {
ITimeEvent event = (ITimeEvent) object;
ITimeGraphDataProvider<? extends TimeGraphEntryModel> provider = getProvider(event.getEntry());
Map<String, String> tooltip = getTooltip(event, event.getTime(), provider, true);
for (Entry<String, String> entry : tooltip.entrySet()) {
String tooltipKey = entry.getKey();
if (tooltipKey.startsWith(DataProviderUtils.ACTION_PREFIX)) {
// It's an action, add it to the menu
menuManager.add(new Action(tooltipKey.substring(DataProviderUtils.ACTION_PREFIX.length())) {
@Override
public void run() {
DataProviderActionUtils.executeAction(entry.getValue());
}
});
}
}
}
}
}
}
private void contributeToActionBars() {
IActionBars bars = getViewSite().getActionBars();
fillLocalToolBar(bars.getToolBarManager());
}
private void fillLocalToolBar(IToolBarManager manager) {
manager.add(getConfigureSymbolsAction());
manager.add(getAggregateByAction());
manager.add(getSortByNameAction());
manager.add(getSortByIdAction());
manager.add(new Separator());
}
private Action getAggregateByAction() {
if (fAggregateByAction == null) {
fAggregateByAction = new Action(Messages.FlameGraphView_GroupByName, IAction.AS_DROP_DOWN_MENU) {
@Override
public void run() {
SortOption sortOption = fSortOption;
if (sortOption == SortOption.BY_NAME) {
setSortOption(SortOption.BY_NAME_REV);
} else {
setSortOption(SortOption.BY_NAME);
}
}
};
fAggregateByAction.setToolTipText(Messages.FlameGraphView_GroupByTooltip);
fAggregateByAction.setImageDescriptor(AGGREGATE_BY_ICON);
fAggregateByAction.setMenuCreator(new IMenuCreator() {
Menu menu = null;
@Override
public void dispose() {
if (menu != null) {
menu.dispose();
menu = null;
}
}
@Override
public Menu getMenu(Control parent) {
if (menu != null) {
menu.dispose();
}
menu = new Menu(parent);
Iterable<ICallGraphProvider> callgraphModules = getCallgraphModules();
Iterator<ICallGraphProvider> iterator = callgraphModules.iterator();
if (!iterator.hasNext()) {
return menu;
}
ICallGraphProvider provider = iterator.next();
// Add the all group element
Action allGroupAction = createActionForGroup(AllGroupDescriptor.getInstance());
new ActionContributionItem(allGroupAction).fill(menu, -1);
Collection<@NonNull IWeightedTreeGroupDescriptor> series = provider.getGroupDescriptors();
series.forEach(group -> {
IWeightedTreeGroupDescriptor subGroup = group;
do {
Action groupAction = createActionForGroup(subGroup);
new ActionContributionItem(groupAction).fill(menu, -1);
subGroup = subGroup.getNextGroup();
} while (subGroup != null);
});
return menu;
}
@Override
public Menu getMenu(Menu parent) {
return null;
}
});
}
return fAggregateByAction;
}
private Action createActionForGroup(IWeightedTreeGroupDescriptor descriptor) {
return new Action(descriptor.getName(), IAction.AS_RADIO_BUTTON) {
@Override
public void run() {
ITmfTrace trace = getTrace();
if (trace == null) {
return;
}
fGroupBy = descriptor;
buildFlameGraph(trace, null, null);
}
};
}
// --------------------------------
// Sorting related methods
// --------------------------------
private Action getSortByNameAction() {
if (fSortByNameAction == null) {
fSortByNameAction = new Action(Messages.FlameGraph_SortByThreadName, IAction.AS_CHECK_BOX) {
@Override
public void run() {
SortOption sortOption = fSortOption;
if (sortOption == SortOption.BY_NAME) {
setSortOption(SortOption.BY_NAME_REV);
} else {
setSortOption(SortOption.BY_NAME);
}
}
};
fSortByNameAction.setToolTipText(Messages.FlameGraph_SortByThreadName);
fSortByNameAction.setImageDescriptor(SORT_BY_NAME_ICON);
}
return fSortByNameAction;
}
private Action getSortByIdAction() {
if (fSortByIdAction == null) {
fSortByIdAction = new Action(Messages.FlameGraph_SortByThreadId, IAction.AS_CHECK_BOX) {
@Override
public void run() {
SortOption sortOption = fSortOption;
if (sortOption == SortOption.BY_ID) {
setSortOption(SortOption.BY_ID_REV);
} else {
setSortOption(SortOption.BY_ID);
}
}
};
fSortByIdAction.setToolTipText(Messages.FlameGraph_SortByThreadId);
fSortByIdAction.setImageDescriptor(SORT_BY_ID_ICON);
}
return fSortByIdAction;
}
private void setSortOption(SortOption sortOption) {
// reset defaults
getSortByNameAction().setChecked(false);
getSortByNameAction().setImageDescriptor(SORT_BY_NAME_ICON);
getSortByIdAction().setChecked(false);
getSortByIdAction().setImageDescriptor(SORT_BY_ID_ICON);
if (sortOption.equals(SortOption.BY_NAME)) {
fSortOption = SortOption.BY_NAME;
getSortByNameAction().setChecked(true);
} else if (sortOption.equals(SortOption.BY_NAME_REV)) {
fSortOption = SortOption.BY_NAME_REV;
getSortByNameAction().setChecked(true);
getSortByNameAction().setImageDescriptor(SORT_BY_NAME_REV_ICON);
} else if (sortOption.equals(SortOption.BY_ID)) {
fSortOption = SortOption.BY_ID;
getSortByIdAction().setChecked(true);
} else if (sortOption.equals(SortOption.BY_ID_REV)) {
fSortOption = SortOption.BY_ID_REV;
getSortByIdAction().setChecked(true);
getSortByIdAction().setImageDescriptor(SORT_BY_ID_REV_ICON);
}
saveSortOption();
refresh();
}
private void saveSortOption() {
SortOption sortOption = fSortOption;
IDialogSettings settings = Activator.getDefault().getDialogSettings();
IDialogSettings section = settings.getSection(getClass().getName());
if (section == null) {
section = settings.addNewSection(getClass().getName());
}
section.put(SORT_OPTION_KEY, sortOption.name());
}
private void loadSortOption() {
IDialogSettings settings = Activator.getDefault().getDialogSettings();
IDialogSettings section = settings.getSection(getClass().getName());
if (section == null) {
return;
}
String sortOption = section.get(SORT_OPTION_KEY);
if (sortOption == null) {
return;
}
setSortOption(SortOption.fromName(sortOption));
}
//--------------------------------
// Symbol related methods
//--------------------------------
private Action getConfigureSymbolsAction() {
if (fConfigureSymbolsAction != null) {
return fConfigureSymbolsAction;
}
fConfigureSymbolsAction = new Action(Messages.FlameGraphView_ConfigureSymbolProvidersText) {
@Override
public void run() {
SymbolProviderConfigDialog dialog = new SymbolProviderConfigDialog(getSite().getShell(), getProviderPages());
if (dialog.open() == IDialogConstants.OK_ID) {
startZoomThread(getTimeGraphViewer().getTime0(), getTimeGraphViewer().getTime1(), true);
}
}
};
fConfigureSymbolsAction.setToolTipText(Messages.FlameGraphView_ConfigureSymbolProvidersTooltip);
fConfigureSymbolsAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath(SYMBOL_MAPPING_ICON_PATH));
/*
* The updateConfigureSymbolsAction() method (called by refresh()) will
* set the action to true if applicable after the symbol provider has
* been properly loaded.
*/
fConfigureSymbolsAction.setEnabled(true);
return fConfigureSymbolsAction;
}
/**
* @return an array of {@link ISymbolProviderPreferencePage} that will
* configure the current traces
*/
private ISymbolProviderPreferencePage[] getProviderPages() {
List<ISymbolProviderPreferencePage> pages = new ArrayList<>();
ITmfTrace trace = fTrace;
if (trace != null) {
for (ITmfTrace subTrace : TmfTraceManager.getTraceSet(trace)) {
for (ISymbolProvider provider : SymbolProviderManager.getInstance().getSymbolProviders(subTrace)) {
if (provider instanceof org.eclipse.tracecompass.tmf.ui.symbols.ISymbolProvider) {
org.eclipse.tracecompass.tmf.ui.symbols.ISymbolProvider provider2 = (org.eclipse.tracecompass.tmf.ui.symbols.ISymbolProvider) provider;
ISymbolProviderPreferencePage page = provider2.createPreferencePage();
if (page != null) {
pages.add(page);
}
}
}
}
}
return pages.toArray(new ISymbolProviderPreferencePage[pages.size()]);
}
/**
* Symbol map provider updated
*
* @param signal
* the signal
*/
@TmfSignalHandler
public void symbolMapUpdated(TmfSymbolProviderUpdatedSignal signal) {
if (signal.getSource() != this) {
startZoomThread(getTimeGraphViewer().getTime0(), getTimeGraphViewer().getTime1(), true);
}
}
@Override
protected @Nullable IAction createSaveAction() {
return SaveImageUtil.createSaveAction(getName(), this::getTimeGraphViewer);
}
/**
* Cancel and restart the zoom thread.
*/
public void restartZoomThread() {
ZoomThread zoomThread = fZoomThread;
if (zoomThread != null) {
// Make sure that the zoom thread is not a restart (resume of the previous)
zoomThread.cancel();
fZoomThread = null;
}
startZoomThread(getTimeGraphViewer().getTime0(), getTimeGraphViewer().getTime1(), true);
}
/**
* Set or remove the global regex filter value
*
* @param signal
* the signal carrying the regex value
*/
@TmfSignalHandler
public void regexFilterApplied(TmfFilterAppliedSignal signal) {
// Restart the zoom thread to apply the new filter
Display.getDefault().asyncExec(() -> restartZoomThread());
}
}