/******************************************************************************* * Copyright (c) 2013, 2016 Ericsson and others. * * 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 * * Contributors: * Patrick Tasse - Initial API and implementation * Bernd Hufmann - Updated signal handling * Marc-Andre Laperle - Map from binary file *******************************************************************************/ package org.eclipse.tracecompass.tmf.ui.views.callstack; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.function.Consumer; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.GroupMarker; import org.eclipse.jface.action.IAction; 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.dialogs.IDialogSettings; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.swt.SWT; 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.internal.tmf.ui.Activator; import org.eclipse.tracecompass.internal.tmf.ui.ITmfImageConstants; import org.eclipse.tracecompass.internal.tmf.ui.Messages; import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem; import org.eclipse.tracecompass.statesystem.core.StateSystemUtils; import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException; import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException; import org.eclipse.tracecompass.statesystem.core.exceptions.StateValueTypeException; import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue; import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue.Type; import org.eclipse.tracecompass.tmf.core.callstack.CallStackAnalysis; 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.timestamp.ITmfTimestamp; import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestampDelta; import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils; import org.eclipse.tracecompass.tmf.ui.editors.ITmfTraceEditor; import org.eclipse.tracecompass.tmf.ui.symbols.ISymbolProvider; import org.eclipse.tracecompass.tmf.ui.symbols.ISymbolProviderPreferencePage; import org.eclipse.tracecompass.tmf.ui.symbols.SymbolProviderConfigDialog; import org.eclipse.tracecompass.tmf.ui.symbols.SymbolProviderManager; import org.eclipse.tracecompass.tmf.ui.views.timegraph.AbstractTimeGraphView; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphTimeListener; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphContentProvider; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphTimeEvent; 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.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.widgets.TimeGraphControl; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchActionConstants; /** * Main implementation for the Call Stack view * * @author Patrick Tasse */ public class CallStackView extends AbstractTimeGraphView { // ------------------------------------------------------------------------ // Constants // ------------------------------------------------------------------------ /** View ID. */ public static final @NonNull String ID = "org.eclipse.linuxtools.tmf.ui.views.callstack"; //$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 String[] FILTER_COLUMN_NAMES = new String[] { Messages.CallStackView_ThreadColumn }; /** Timeout between updates in the build thread in ms */ private static final long BUILD_UPDATE_TIMEOUT = 500; private static final Image PROCESS_IMAGE = Activator.getDefault().getImageFromPath("icons/obj16/process_obj.gif"); //$NON-NLS-1$ private static final Image THREAD_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$ 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 SORT_BY_TIME_ICON = Activator.getDefault().getImageDescripterFromPath("icons/etool16/sort_time.gif"); //$NON-NLS-1$ private static final ImageDescriptor SORT_BY_TIME_REV_ICON = Activator.getDefault().getImageDescripterFromPath("icons/etool16/sort_time_rev.gif"); //$NON-NLS-1$ private static final String SORT_OPTION_KEY = "sort.option"; //$NON-NLS-1$ private enum SortOption { BY_NAME, BY_NAME_REV, BY_ID, BY_ID_REV, BY_TIME, BY_TIME_REV } private @NonNull SortOption fSortOption = SortOption.BY_NAME; private @NonNull Comparator<ITimeGraphEntry> fThreadComparator = new ThreadNameComparator(false); private Action fSortByNameAction; private Action fSortByIdAction; private Action fSortByTimeAction; // ------------------------------------------------------------------------ // Fields // ------------------------------------------------------------------------ private final Map<ITmfTrace, ISymbolProvider> fSymbolProviders = new HashMap<>(); // The next event action private Action fNextEventAction; // The previous event action private Action fPrevEventAction; // The next item action private Action fNextItemAction; // The previous item action private Action fPreviousItemAction; // The action to import a binary file mapping */ private Action fConfigureSymbolsAction; // The saved time sync. signal used when switching off the pinning of a view private TmfSelectionRangeUpdatedSignal fSavedTimeSyncSignal; // The saved window range signal used when switching off the pinning of // a view private TmfWindowRangeUpdatedSignal fSavedRangeSyncSignal; // 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; // ------------------------------------------------------------------------ // Classes // ------------------------------------------------------------------------ private static class TraceEntry extends TimeGraphEntry { public TraceEntry(String name, long startTime, long endTime) { super(name, startTime, endTime); } @Override public boolean hasTimeEvents() { return false; } } private static class ProcessEntry extends TimeGraphEntry { private final int fProcessId; public ProcessEntry(String name, int processId, long startTime, long endTime) { super(name, startTime, endTime); fProcessId = processId; } @Override public boolean hasTimeEvents() { return false; } } private static class ThreadEntry extends TimeGraphEntry { // The thread id private final long fThreadId; public ThreadEntry(String name, long threadId, long startTime, long endTime) { super(name, startTime, endTime); fThreadId = threadId; } @Override public boolean hasTimeEvents() { return false; } public long getThreadId() { return fThreadId; } } private class CallStackComparator implements Comparator<ITimeGraphEntry> { @Override public int compare(ITimeGraphEntry o1, ITimeGraphEntry o2) { if (o1 instanceof ThreadEntry && o2 instanceof ThreadEntry) { return fThreadComparator.compare(o1, o2); } else if (o1 instanceof ProcessEntry && o2 instanceof ProcessEntry) { return Integer.compare(((ProcessEntry) o1).fProcessId, ((ProcessEntry) o2).fProcessId); } return 0; } } private static class ThreadNameComparator implements Comparator<ITimeGraphEntry> { private boolean reverse; public ThreadNameComparator(boolean reverse) { this.reverse = reverse; } @Override public int compare(ITimeGraphEntry o1, ITimeGraphEntry o2) { return reverse ? o2.getName().compareTo(o1.getName()) : o1.getName().compareTo(o2.getName()); } } private static class ThreadIdComparator implements Comparator<ITimeGraphEntry> { private boolean reverse; public ThreadIdComparator(boolean reverse) { this.reverse = reverse; } @Override public int compare(ITimeGraphEntry o1, ITimeGraphEntry o2) { if (o1 instanceof ThreadEntry && o2 instanceof ThreadEntry) { ThreadEntry t1 = (ThreadEntry) o1; ThreadEntry t2 = (ThreadEntry) o2; return reverse ? Long.compare(t2.getThreadId(), t1.getThreadId()) : Long.compare(t1.getThreadId(), t2.getThreadId()); } return 0; } } private static class ThreadTimeComparator implements Comparator<ITimeGraphEntry> { private boolean reverse; public ThreadTimeComparator(boolean reverse) { this.reverse = reverse; } @Override public int compare(ITimeGraphEntry o1, ITimeGraphEntry o2) { return reverse ? Long.compare(o2.getStartTime(), o1.getStartTime()) : Long.compare(o1.getStartTime(), o2.getStartTime()); } } private static class CallStackTreeLabelProvider extends TreeLabelProvider { @Override public Image getColumnImage(Object element, int columnIndex) { if (columnIndex == 0) { if (element instanceof ProcessEntry) { return PROCESS_IMAGE; } else if (element instanceof ThreadEntry) { return THREAD_IMAGE; } else if (element instanceof CallStackEntry) { CallStackEntry entry = (CallStackEntry) element; if (entry.getFunctionName().length() > 0) { return STACKFRAME_IMAGE; } } } return null; } @Override public String getColumnText(Object element, int columnIndex) { if (element instanceof CallStackEntry) { CallStackEntry entry = (CallStackEntry) element; if (columnIndex == 0) { return entry.getFunctionName(); } else if (columnIndex == 1 && entry.getFunctionName().length() > 0) { int depth = entry.getStackLevel(); return Integer.toString(depth); } else if (columnIndex == 2 && entry.getFunctionName().length() > 0) { ITmfTimestamp ts = TmfTimestamp.fromNanos(entry.getFunctionEntryTime()); return ts.toString(); } else if (columnIndex == 3 && entry.getFunctionName().length() > 0) { ITmfTimestamp ts = TmfTimestamp.fromNanos(entry.getFunctionExitTime()); return ts.toString(); } else if (columnIndex == 4 && entry.getFunctionName().length() > 0) { ITmfTimestamp ts = new TmfTimestampDelta(entry.getFunctionExitTime() - entry.getFunctionEntryTime(), ITmfTimestamp.NANOSECOND_SCALE); return ts.toString(); } } else if (element instanceof ITimeGraphEntry) { if (columnIndex == 0) { return ((ITimeGraphEntry) element).getName(); } } return ""; //$NON-NLS-1$ } } private class CallStackFilterContentProvider extends TimeGraphContentProvider { @Override public boolean hasChildren(Object element) { if (element instanceof TraceEntry) { return super.hasChildren(element); } return false; } @Override public ITimeGraphEntry[] getChildren(Object parentElement) { if (parentElement instanceof TraceEntry) { return super.getChildren(parentElement); } return new ITimeGraphEntry[0]; } } // ------------------------------------------------------------------------ // Constructors // ------------------------------------------------------------------------ /** * Default constructor */ public CallStackView() { super(ID, new CallStackPresentationProvider()); getPresentationProvider().setCallStackView(this); setTreeColumns(COLUMN_NAMES); setTreeLabelProvider(new CallStackTreeLabelProvider()); setEntryComparator(new CallStackComparator()); setFilterColumns(FILTER_COLUMN_NAMES); setFilterContentProvider(new CallStackFilterContentProvider()); setFilterLabelProvider(new CallStackTreeLabelProvider()); } // ------------------------------------------------------------------------ // ViewPart // ------------------------------------------------------------------------ @Override public void createPartControl(Composite parent) { super.createPartControl(parent); getTimeGraphViewer().addTimeListener(new ITimeGraphTimeListener() { @Override public void timeSelected(TimeGraphTimeEvent event) { synchingToTime(event.getBeginTime()); } }); getTimeGraphViewer().getTimeGraphControl().addMouseListener(new MouseAdapter() { @Override public void mouseDoubleClick(MouseEvent event) { Object selection = getTimeGraphViewer().getSelection(); if (selection instanceof CallStackEntry) { CallStackEntry entry = (CallStackEntry) selection; if (entry.getFunctionName().length() > 0) { long entryTime = entry.getFunctionEntryTime(); long exitTime = entry.getFunctionExitTime(); TmfTimeRange range = new TmfTimeRange(TmfTimestamp.fromNanos(entryTime), TmfTimestamp.fromNanos(exitTime)); broadcast(new TmfWindowRangeUpdatedSignal(CallStackView.this, range)); getTimeGraphViewer().setStartFinishTime(entryTime, exitTime); startZoomThread(entryTime, exitTime); } } } }); getTimeGraphViewer().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 CallStackEvent) { CallStackEvent event = (CallStackEvent) object; long startTime = event.getTime(); long endTime = startTime + event.getDuration(); TmfTimeRange range = new TmfTimeRange(TmfTimestamp.fromNanos(startTime), TmfTimestamp.fromNanos(endTime)); broadcast(new TmfWindowRangeUpdatedSignal(CallStackView.this, range)); getTimeGraphViewer().setStartFinishTime(startTime, endTime); startZoomThread(startTime, endTime); break; } } } } }); contributeToActionBars(); loadSortOption(); 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 * @since 1.0 */ @Override @TmfSignalHandler public void selectionRangeUpdated(final TmfSelectionRangeUpdatedSignal signal) { fSavedTimeSyncSignal = isPinned() ? new TmfSelectionRangeUpdatedSignal(signal.getSource(), signal.getBeginTime(), signal.getEndTime()) : null; if (signal.getSource() == this || getTrace() == null || isPinned()) { return; } final long beginTime = signal.getBeginTime().toNanos(); final long endTime = signal.getEndTime().toNanos(); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (getTimeGraphViewer().getControl().isDisposed()) { return; } if (beginTime == endTime) { getTimeGraphViewer().setSelectedTime(beginTime, true); } else { getTimeGraphViewer().setSelectionRange(beginTime, endTime, true); } fSyncSelection = true; synchingToTime(beginTime); fSyncSelection = false; startZoomThread(getTimeGraphViewer().getTime0(), getTimeGraphViewer().getTime1()); } }); } /** * @since 2.0 */ @Override @TmfSignalHandler public void windowRangeUpdated(final TmfWindowRangeUpdatedSignal signal) { if (isPinned()) { fSavedRangeSyncSignal = new TmfWindowRangeUpdatedSignal(signal.getSource(), signal.getCurrentRange()); fSavedTimeSyncSignal = null; } if ((signal.getSource() == this) || isPinned()) { return; } super.windowRangeUpdated(signal); } // ------------------------------------------------------------------------ // Internal // ------------------------------------------------------------------------ /** * @since 2.1 */ @Override protected CallStackPresentationProvider getPresentationProvider() { /* Set to this type by the constructor */ return (CallStackPresentationProvider) super.getPresentationProvider(); } @Override @TmfSignalHandler public void traceClosed(TmfTraceClosedSignal signal) { super.traceClosed(signal); synchronized(fSymbolProviders){ for(ITmfTrace trace : getTracesToBuild(signal.getTrace())){ fSymbolProviders.remove(trace); } } } /** * @since 2.0 */ @Override protected void refresh() { super.refresh(); updateConfigureSymbolsAction(); } @Override protected void buildEntryList(final ITmfTrace trace, final ITmfTrace parentTrace, final IProgressMonitor monitor) { if (monitor.isCanceled()) { return; } /* * Load the symbol provider for the current trace, even if it does not * provide a call stack analysis module. See * https://bugs.eclipse.org/bugs/show_bug.cgi?id=494212 */ ISymbolProvider provider = fSymbolProviders.get(trace); if (provider == null) { provider = SymbolProviderManager.getInstance().getSymbolProvider(trace); provider.loadConfiguration(null); fSymbolProviders.put(trace, provider); } /* Continue with the call stack view specific operations */ CallStackAnalysis module = getCallStackModule(trace); if (module == null) { addUnavailableEntry(trace, parentTrace); return; } ITmfStateSystem ss = module.getStateSystem(); if (ss == null) { addUnavailableEntry(trace, parentTrace); return; } Map<ITmfTrace, TraceEntry> traceEntryMap = new HashMap<>(); Map<Integer, ProcessEntry> processEntryMap = new HashMap<>(); Map<Integer, ThreadEntry> threadEntryMap = new HashMap<>(); long start = ss.getStartTime(); boolean complete = false; while (!complete) { if (monitor.isCanceled()) { return; } complete = ss.waitUntilBuilt(BUILD_UPDATE_TIMEOUT); if (ss.isCancelled()) { return; } long end = ss.getCurrentEndTime(); if (start == end && !complete) { // when complete execute one last time regardless of end time continue; } TraceEntry traceEntry = traceEntryMap.get(trace); if (traceEntry == null) { traceEntry = new TraceEntry(trace.getName(), start, end + 1); traceEntryMap.put(trace, traceEntry); traceEntry.sortChildren(fThreadComparator); addToEntryList(parentTrace, Collections.singletonList(traceEntry)); } else { traceEntry.updateEndTime(end); } try { /* * Get quarks first to make sure they are in the full query result. */ List<Integer> processQuarks = ss.getQuarks(module.getProcessesPattern()); List<ITmfStateInterval> endStates = ss.queryFullState(end); for (int processQuark : processQuarks) { /* * Default to trace entry, overwrite if a process entry exists. */ TimeGraphEntry threadParent = traceEntry; int processId = -1; if (processQuark != ITmfStateSystem.ROOT_ATTRIBUTE) { /* Create the entry for the process */ ProcessEntry processEntry = processEntryMap.get(processQuark); if (processEntry == null) { String processName = ss.getAttributeName(processQuark); ITmfStateValue processStateValue = endStates.get(processQuark).getStateValue(); if (processStateValue.getType() == Type.INTEGER) { processId = processStateValue.unboxInt(); } else { try { processId = Integer.parseInt(processName); } catch (NumberFormatException e) { /* use default processId */ } } processEntry = new ProcessEntry(processName, processId, start, end); processEntryMap.put(processQuark, processEntry); traceEntry.addChild(processEntry); } else { processEntry.updateEndTime(end); } /* The parent of the thread entries will be a process */ threadParent = processEntry; } /* Create the threads under the process */ List<Integer> threadQuarks = ss.getQuarks(processQuark, module.getThreadsPattern()); /* * Only query startStates if necessary (threadEntry == null) */ List<ITmfStateInterval> startStates = null; for (int threadQuark : threadQuarks) { if (monitor.isCanceled()) { return; } String[] callStackPath = module.getCallStackPath(); int callStackQuark = ss.getQuarkRelative(threadQuark, callStackPath); String threadName = ss.getAttributeName(threadQuark); long threadEnd = end + 1; if (callStackQuark >= endStates.size()) { /* attribute created after previous full query */ endStates = ss.queryFullState(end); } ITmfStateInterval endInterval = endStates.get(callStackQuark); if (endInterval.getStateValue().isNull() && endInterval.getStartTime() != ss.getStartTime()) { threadEnd = endInterval.getStartTime(); } /* * Default to process/trace entry, overwrite if a thread entry exists. */ TimeGraphEntry callStackParent = threadParent; if (threadQuark != processQuark) { ThreadEntry threadEntry = threadEntryMap.get(threadQuark); if (threadEntry == null) { if (startStates == null || callStackQuark >= startStates.size()) { /* attribute created after previous full query */ startStates = ss.queryFullState(ss.getStartTime()); } long threadId = -1; if (threadQuark >= endStates.size()) { /* attribute created after previous full query */ endStates = ss.queryFullState(end); } ITmfStateValue threadStateValue = endStates.get(threadQuark).getStateValue(); if (threadStateValue.getType() == Type.LONG || threadStateValue.getType() == Type.INTEGER) { threadId = threadStateValue.unboxLong(); } else { try { threadId = Long.parseLong(threadName); } catch (NumberFormatException e) { /* use default threadId */ } } long threadStart = start; ITmfStateInterval startInterval = startStates.get(callStackQuark); if (startInterval.getStateValue().isNull()) { threadStart = Math.min(startInterval.getEndTime() + 1, end + 1); } threadEntry = new ThreadEntry(threadName, threadId, threadStart, threadEnd); threadEntryMap.put(threadQuark, threadEntry); threadParent.addChild(threadEntry); } else { threadEntry.updateEndTime(threadEnd); } /* The parent of the call stack entries will be a thread */ callStackParent = threadEntry; } int level = 1; for (int stackLevelQuark : ss.getSubAttributes(callStackQuark, false)) { if (level > callStackParent.getChildren().size()) { CallStackEntry callStackEntry = new CallStackEntry(threadName, stackLevelQuark, level, processId, trace, ss); callStackParent.addChild(callStackEntry); } level++; } } } } catch (AttributeNotFoundException e) { Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$ } catch (StateSystemDisposedException e) { /* Ignored */ } if (parentTrace == getTrace()) { synchronized (this) { setStartTime(getStartTime() == SWT.DEFAULT ? start : Math.min(getStartTime(), start)); setEndTime(getEndTime() == SWT.DEFAULT ? end + 1 : Math.max(getEndTime(), end + 1)); } synchingToTime(getTimeGraphViewer().getSelectionBegin()); refresh(); } Consumer<TimeGraphEntry> consumer = new Consumer<TimeGraphEntry>() { @Override public void accept(TimeGraphEntry entry) { if (monitor.isCanceled()) { return; } if (entry instanceof CallStackEntry) { buildStatusEvents(parentTrace, (CallStackEntry) entry, monitor, ss.getStartTime(), end); return; } entry.getChildren().forEach(this); } }; traceEntry.getChildren().forEach(consumer); start = end; } } private void addUnavailableEntry(ITmfTrace trace, ITmfTrace parentTrace) { String name = Messages.CallStackView_StackInfoNotAvailable + ' ' + '(' + trace.getName() + ')'; TraceEntry unavailableEntry = new TraceEntry(name, 0, 0); addToEntryList(parentTrace, Collections.singletonList(unavailableEntry)); if (parentTrace == getTrace()) { refresh(); } } private void buildStatusEvents(ITmfTrace trace, CallStackEntry entry, @NonNull IProgressMonitor monitor, long start, long end) { ITmfStateSystem ss = entry.getStateSystem(); long resolution = Math.max(1, (end - ss.getStartTime()) / getDisplayWidth()); List<ITimeEvent> eventList = getEventList(entry, start, end + 1, resolution, monitor); if (eventList != null) { entry.setEventList(eventList); } if (trace == getTrace()) { redraw(); } } /** * @since 1.2 */ @Override protected final List<ITimeEvent> getEventList(TimeGraphEntry tgentry, long startTime, long endTime, long resolution, IProgressMonitor monitor) { if (!(tgentry instanceof CallStackEntry)) { return null; } CallStackEntry entry = (CallStackEntry) tgentry; ITmfStateSystem ss = entry.getStateSystem(); long start = Math.max(startTime, ss.getStartTime()); long end = Math.min(endTime, ss.getCurrentEndTime() + 1); if (end <= start) { return null; } boolean isZoomThread = Thread.currentThread() instanceof ZoomThread; List<ITimeEvent> eventList = null; try { List<ITmfStateInterval> stackIntervals = StateSystemUtils.queryHistoryRange(ss, entry.getQuark(), start, end - 1, resolution, monitor); eventList = new ArrayList<>(stackIntervals.size()); long lastEndTime = -1; boolean lastIsNull = false; for (ITmfStateInterval statusInterval : stackIntervals) { if (monitor.isCanceled()) { return null; } long time = statusInterval.getStartTime(); long duration = statusInterval.getEndTime() - time + 1; if (!statusInterval.getStateValue().isNull()) { final int modulo = CallStackPresentationProvider.NUM_COLORS / 2; int value = statusInterval.getStateValue().toString().hashCode() % modulo + modulo; eventList.add(new CallStackEvent(entry, time, duration, value)); lastIsNull = false; } else { if (lastEndTime == -1 && isZoomThread) { // add null event if it intersects the start time eventList.add(new NullTimeEvent(entry, time, duration)); } else { if (lastEndTime != time && lastIsNull) { // add unknown event if between two null states eventList.add(new TimeEvent(entry, lastEndTime, time - lastEndTime)); } if (time + duration >= endTime && isZoomThread) { // add null event if it intersects the end time eventList.add(new NullTimeEvent(entry, time, duration)); } } lastIsNull = true; } lastEndTime = time + duration; } } catch (AttributeNotFoundException e) { Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$ } catch (TimeRangeException e) { Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$ } catch (StateSystemDisposedException e) { /* Ignored */ } return eventList; } /** * @since 1.2 */ @Override protected void synchingToTime(final long time) { List<TimeGraphEntry> traceEntries = getEntryList(getTrace()); Map<ITmfStateSystem, List<ITmfStateInterval>> fullStateMap = new HashMap<>(); if (traceEntries == null) { return; } Consumer<TimeGraphEntry> consumer = new Consumer<TimeGraphEntry>() { @Override public void accept(TimeGraphEntry entry) { if (entry instanceof CallStackEntry) { CallStackEntry callStackEntry = (CallStackEntry) entry; ITmfStateSystem ss = callStackEntry.getStateSystem(); if (time < ss.getStartTime() || time > ss.getCurrentEndTime()) { return; } ITmfTrace trace = callStackEntry.getTrace(); try { List<ITmfStateInterval> fullState = getFullState(ss); ITmfStateInterval stackLevelInterval = fullState.get(callStackEntry.getQuark()); ITmfStateValue nameValue = stackLevelInterval.getStateValue(); String name = getFunctionName(trace, callStackEntry.getProcessId(), time, nameValue); callStackEntry.setFunctionName(name); if (!name.isEmpty()) { callStackEntry.setFunctionEntryTime(stackLevelInterval.getStartTime()); callStackEntry.setFunctionExitTime(stackLevelInterval.getEndTime() + 1); } if (fSyncSelection) { int callStackQuark = ss.getParentAttributeQuark(callStackEntry.getQuark()); ITmfStateInterval stackInterval = fullState.get(callStackQuark); if (time == stackInterval.getStartTime()) { ITmfStateValue stackLevelState = stackInterval.getStateValue(); if (stackLevelState.unboxInt() == callStackEntry.getStackLevel() || stackLevelState.isNull()) { fSyncSelection = false; Display.getDefault().asyncExec(() -> { getTimeGraphViewer().setSelection(callStackEntry, true); getTimeGraphViewer().getTimeGraphControl().fireSelectionChanged(); }); } } } } catch (StateSystemDisposedException e) { /* Ignored */ } return; } entry.getChildren().forEach(this); } private List<ITmfStateInterval> getFullState(ITmfStateSystem ss) throws StateSystemDisposedException { List<ITmfStateInterval> fullState = fullStateMap.get(ss); if (fullState == null) { fullState = ss.queryFullState(time); fullStateMap.put(ss, fullState); } return fullState; } }; traceEntries.forEach(consumer); if (Display.getCurrent() != null) { getTimeGraphViewer().refresh(); } } String getFunctionName(ITmfTrace trace, int processId, long timestamp, ITmfStateValue nameValue) { long address = Long.MAX_VALUE; String name = ""; //$NON-NLS-1$ try { if (nameValue.getType() == Type.STRING) { name = nameValue.unboxStr(); try { address = Long.parseLong(name, 16); } catch (NumberFormatException e) { // ignore } } else if (nameValue.getType() == Type.INTEGER) { name = "0x" + Integer.toUnsignedString(nameValue.unboxInt(), 16); //$NON-NLS-1$ address = nameValue.unboxInt(); } else if (nameValue.getType() == Type.LONG) { name = "0x" + Long.toUnsignedString(nameValue.unboxLong(), 16); //$NON-NLS-1$ address = nameValue.unboxLong(); } } catch (StateValueTypeException e) { } if (address != Long.MAX_VALUE) { ISymbolProvider provider = fSymbolProviders.get(trace); if (provider != null) { String symbol = provider.getSymbolText(processId, timestamp, address); if (symbol != null) { name = symbol; } } } return name; } private void makeActions() { fPreviousItemAction = getTimeGraphViewer().getPreviousItemAction(); fPreviousItemAction.setText(Messages.TmfTimeGraphViewer_PreviousItemActionNameText); fPreviousItemAction.setToolTipText(Messages.TmfTimeGraphViewer_PreviousItemActionToolTipText); fNextItemAction = getTimeGraphViewer().getNextItemAction(); fNextItemAction.setText(Messages.TmfTimeGraphViewer_NextItemActionNameText); fNextItemAction.setToolTipText(Messages.TmfTimeGraphViewer_NextItemActionToolTipText); } private void contributeToActionBars() { // Create pin action contributePinActionToToolBar(); fPinAction.addPropertyChangeListener(new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { if (IAction.CHECKED.equals(event.getProperty()) && !isPinned()) { if (fSavedRangeSyncSignal != null) { windowRangeUpdated(fSavedRangeSyncSignal); fSavedRangeSyncSignal = null; } if (fSavedTimeSyncSignal != null) { selectionRangeUpdated(fSavedTimeSyncSignal); fSavedTimeSyncSignal = null; } } } }); } /** * @since 1.2 */ @Override protected void fillLocalToolBar(IToolBarManager manager) { makeActions(); manager.add(getConfigureSymbolsAction()); manager.add(new Separator()); manager.add(getSortByNameAction()); manager.add(getSortByIdAction()); manager.add(getSortByTimeAction()); manager.add(new Separator()); manager.add(getTimeGraphViewer().getShowFilterDialogAction()); manager.add(new Separator()); manager.add(getTimeGraphViewer().getResetScaleAction()); manager.add(getPreviousEventAction()); manager.add(getNextEventAction()); manager.add(new Separator()); manager.add(getTimeGraphViewer().getToggleBookmarkAction()); manager.add(getTimeGraphViewer().getPreviousMarkerAction()); manager.add(getTimeGraphViewer().getNextMarkerAction()); manager.add(new Separator()); manager.add(fPreviousItemAction); manager.add(fNextItemAction); manager.add(getTimeGraphViewer().getZoomInAction()); manager.add(getTimeGraphViewer().getZoomOutAction()); } /** * @since 2.0 */ @Override protected void fillTimeGraphEntryContextMenu(IMenuManager contextMenu) { contextMenu.add(new GroupMarker(IWorkbenchActionConstants.GROUP_REORGANIZE)); contextMenu.add(getSortByNameAction()); contextMenu.add(getSortByIdAction()); contextMenu.add(getSortByTimeAction()); } /** * Get the the next event action. * * @return The action object */ private Action getNextEventAction() { if (fNextEventAction == null) { fNextEventAction = new Action() { @Override public void run() { TimeGraphViewer viewer = getTimeGraphViewer(); ITimeGraphEntry entry = viewer.getSelection(); if (entry instanceof CallStackEntry) { try { CallStackEntry callStackEntry = (CallStackEntry) entry; ITmfStateSystem ss = callStackEntry.getStateSystem(); long time = Math.max(ss.getStartTime(), Math.min(ss.getCurrentEndTime(), viewer.getSelectionBegin())); TimeGraphEntry parentEntry = callStackEntry.getParent(); int quark = ss.getParentAttributeQuark(callStackEntry.getQuark()); ITmfStateInterval stackInterval = ss.querySingleState(time, quark); long newTime = stackInterval.getEndTime() + 1; viewer.setSelectedTimeNotify(newTime, true); stackInterval = ss.querySingleState(Math.min(ss.getCurrentEndTime(), newTime), quark); int stackLevel = stackInterval.getStateValue().unboxInt(); ITimeGraphEntry selectedEntry = parentEntry.getChildren().get(Math.max(0, stackLevel - 1)); viewer.setSelection(selectedEntry, true); viewer.getTimeGraphControl().fireSelectionChanged(); startZoomThread(viewer.getTime0(), viewer.getTime1()); } catch (TimeRangeException | StateSystemDisposedException | StateValueTypeException e) { Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$ } } } }; fNextEventAction.setText(Messages.TmfTimeGraphViewer_NextStateChangeActionNameText); fNextEventAction.setToolTipText(Messages.TmfTimeGraphViewer_NextStateChangeActionToolTipText); fNextEventAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_NEXT_STATE_CHANGE)); } return fNextEventAction; } /** * Get the previous event action. * * @return The Action object */ private Action getPreviousEventAction() { if (fPrevEventAction == null) { fPrevEventAction = new Action() { @Override public void run() { TimeGraphViewer viewer = getTimeGraphViewer(); ITimeGraphEntry entry = viewer.getSelection(); if (entry instanceof CallStackEntry) { try { CallStackEntry callStackEntry = (CallStackEntry) entry; ITmfStateSystem ss = callStackEntry.getStateSystem(); long time = Math.max(ss.getStartTime(), Math.min(ss.getCurrentEndTime(), viewer.getSelectionBegin())); TimeGraphEntry parentEntry = callStackEntry.getParent(); int quark = ss.getParentAttributeQuark(callStackEntry.getQuark()); ITmfStateInterval stackInterval = ss.querySingleState(time, quark); if (stackInterval.getStartTime() == time && time > ss.getStartTime()) { stackInterval = ss.querySingleState(time - 1, quark); } viewer.setSelectedTimeNotify(stackInterval.getStartTime(), true); int stackLevel = stackInterval.getStateValue().unboxInt(); ITimeGraphEntry selectedEntry = parentEntry.getChildren().get(Math.max(0, stackLevel - 1)); viewer.setSelection(selectedEntry, true); viewer.getTimeGraphControl().fireSelectionChanged(); startZoomThread(viewer.getTime0(), viewer.getTime1()); } catch (TimeRangeException | StateSystemDisposedException | StateValueTypeException e) { Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$ } } } }; fPrevEventAction.setText(Messages.TmfTimeGraphViewer_PreviousStateChangeActionNameText); fPrevEventAction.setToolTipText(Messages.TmfTimeGraphViewer_PreviousStateChangeActionToolTipText); fPrevEventAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_PREV_STATE_CHANGE)); } return fPrevEventAction; } private static @Nullable CallStackAnalysis getCallStackModule(@NonNull ITmfTrace trace) { /* * Since we cannot know the exact analysis ID (in separate plugins), we * will search using the analysis type. */ Iterable<CallStackAnalysis> modules = TmfTraceUtils.getAnalysisModulesOfClass(trace, CallStackAnalysis.class); Iterator<CallStackAnalysis> it = modules.iterator(); if (!it.hasNext()) { /* This trace does not provide a call-stack analysis */ return null; } /* * We only look at the first module we find. * * TODO Handle the advanced case where one trace provides more than one * call-stack analysis. */ CallStackAnalysis module = it.next(); /* This analysis is not automatic, we need to schedule it on-demand */ module.schedule(); if (!module.waitForInitialization()) { /* The initialization did not succeed */ return null; } return module; } // ------------------------------------------------------------------------ // Methods related to function name mapping // ------------------------------------------------------------------------ private Action getSortByNameAction() { if (fSortByNameAction == null) { fSortByNameAction = new Action(Messages.CallStackView_SortByThreadName, IAction.AS_CHECK_BOX) { @Override public void run() { if (fSortOption == SortOption.BY_NAME) { saveSortOption(SortOption.BY_NAME_REV); } else { saveSortOption(SortOption.BY_NAME); } } }; fSortByNameAction.setToolTipText(Messages.CallStackView_SortByThreadName); fSortByNameAction.setImageDescriptor(SORT_BY_NAME_ICON); } return fSortByNameAction; } private Action getSortByIdAction() { if (fSortByIdAction == null) { fSortByIdAction = new Action(Messages.CallStackView_SortByThreadId, IAction.AS_CHECK_BOX) { @Override public void run() { if (fSortOption == SortOption.BY_ID) { saveSortOption(SortOption.BY_ID_REV); } else { saveSortOption(SortOption.BY_ID); } } }; fSortByIdAction.setToolTipText(Messages.CallStackView_SortByThreadId); fSortByIdAction.setImageDescriptor(SORT_BY_ID_ICON); } return fSortByIdAction; } private Action getSortByTimeAction() { if (fSortByTimeAction == null) { fSortByTimeAction = new Action(Messages.CallStackView_SortByThreadTime, IAction.AS_CHECK_BOX) { @Override public void run() { if (fSortOption == SortOption.BY_TIME) { saveSortOption(SortOption.BY_TIME_REV); } else { saveSortOption(SortOption.BY_TIME); } } }; fSortByTimeAction.setToolTipText(Messages.CallStackView_SortByThreadTime); fSortByTimeAction.setImageDescriptor(SORT_BY_TIME_ICON); } return fSortByTimeAction; } 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; } // reset defaults getSortByNameAction().setChecked(false); getSortByNameAction().setImageDescriptor(SORT_BY_NAME_ICON); getSortByIdAction().setChecked(false); getSortByIdAction().setImageDescriptor(SORT_BY_ID_ICON); getSortByTimeAction().setChecked(false); getSortByTimeAction().setImageDescriptor(SORT_BY_TIME_ICON); if (sortOption.equals(SortOption.BY_NAME.name())) { fSortOption = SortOption.BY_NAME; fThreadComparator = new ThreadNameComparator(false); getSortByNameAction().setChecked(true); } else if (sortOption.equals(SortOption.BY_NAME_REV.name())) { fSortOption = SortOption.BY_NAME_REV; fThreadComparator = new ThreadNameComparator(true); getSortByNameAction().setChecked(true); getSortByNameAction().setImageDescriptor(SORT_BY_NAME_REV_ICON); } else if (sortOption.equals(SortOption.BY_ID.name())) { fSortOption = SortOption.BY_ID; fThreadComparator = new ThreadIdComparator(false); getSortByIdAction().setChecked(true); } else if (sortOption.equals(SortOption.BY_ID_REV.name())) { fSortOption = SortOption.BY_ID_REV; fThreadComparator = new ThreadIdComparator(true); getSortByIdAction().setChecked(true); getSortByIdAction().setImageDescriptor(SORT_BY_ID_REV_ICON); } else if (sortOption.equals(SortOption.BY_TIME.name())) { fSortOption = SortOption.BY_TIME; fThreadComparator = new ThreadTimeComparator(false); getSortByTimeAction().setChecked(true); } else if (sortOption.equals(SortOption.BY_TIME_REV.name())) { fSortOption = SortOption.BY_TIME_REV; fThreadComparator = new ThreadTimeComparator(true); getSortByTimeAction().setChecked(true); getSortByTimeAction().setImageDescriptor(SORT_BY_TIME_REV_ICON); } } private void saveSortOption(SortOption sortOption) { 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()); loadSortOption(); List<TimeGraphEntry> entryList = getEntryList(getTrace()); if (entryList == null) { return; } for (TimeGraphEntry traceEntry : entryList) { traceEntry.sortChildren(fThreadComparator); } refresh(); } 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) { getPresentationProvider().resetFunctionNames(); refresh(); } } }; fConfigureSymbolsAction.setToolTipText(Messages.CallStackView_ConfigureSymbolProvidersTooltip); fConfigureSymbolsAction.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. */ fConfigureSymbolsAction.setEnabled(false); return fConfigureSymbolsAction; } /** * @return an array of {@link ISymbolProviderPreferencePage} that will * configure the current traces */ private ISymbolProviderPreferencePage[] getProviderPages() { List<ISymbolProviderPreferencePage> pages = new ArrayList<>(); ITmfTrace trace = getTrace(); if (trace != null) { for (ITmfTrace subTrace : getTracesToBuild(trace)) { ISymbolProvider provider = fSymbolProviders.get(subTrace); if (provider != null) { 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); } }