/******************************************************************************* * Copyright (c) 2013, 2014 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 * * Contributors: * Patrick Tasse - Initial API and implementation * Bernd Hufmann - Updated signal handling *******************************************************************************/ package fr.inria.linuxtools.tmf.ui.views.callstack; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; 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.jobs.Job; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; 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.swt.widgets.FileDialog; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IEditorPart; import fr.inria.linuxtools.internal.tmf.ui.Activator; import fr.inria.linuxtools.internal.tmf.ui.ITmfImageConstants; import fr.inria.linuxtools.internal.tmf.ui.Messages; import fr.inria.linuxtools.statesystem.core.ITmfStateSystem; import fr.inria.linuxtools.statesystem.core.exceptions.AttributeNotFoundException; import fr.inria.linuxtools.statesystem.core.exceptions.StateSystemDisposedException; import fr.inria.linuxtools.statesystem.core.exceptions.StateValueTypeException; import fr.inria.linuxtools.statesystem.core.exceptions.TimeRangeException; import fr.inria.linuxtools.statesystem.core.interval.ITmfStateInterval; import fr.inria.linuxtools.statesystem.core.statevalue.ITmfStateValue; import fr.inria.linuxtools.statesystem.core.statevalue.ITmfStateValue.Type; import fr.inria.linuxtools.tmf.core.callstack.CallStackStateProvider; import fr.inria.linuxtools.tmf.core.signal.TmfRangeSynchSignal; import fr.inria.linuxtools.tmf.core.signal.TmfSignalHandler; import fr.inria.linuxtools.tmf.core.signal.TmfTimeSynchSignal; import fr.inria.linuxtools.tmf.core.signal.TmfTraceClosedSignal; import fr.inria.linuxtools.tmf.core.signal.TmfTraceOpenedSignal; import fr.inria.linuxtools.tmf.core.signal.TmfTraceSelectedSignal; import fr.inria.linuxtools.tmf.core.timestamp.ITmfTimestamp; import fr.inria.linuxtools.tmf.core.timestamp.TmfNanoTimestamp; import fr.inria.linuxtools.tmf.core.timestamp.TmfTimeRange; import fr.inria.linuxtools.tmf.core.timestamp.TmfTimestamp; import fr.inria.linuxtools.tmf.core.timestamp.TmfTimestampDelta; import fr.inria.linuxtools.tmf.core.trace.ITmfTrace; import fr.inria.linuxtools.tmf.core.trace.TmfTraceManager; import fr.inria.linuxtools.tmf.ui.editors.ITmfTraceEditor; import fr.inria.linuxtools.tmf.ui.views.TmfView; import fr.inria.linuxtools.tmf.ui.widgets.timegraph.ITimeGraphRangeListener; import fr.inria.linuxtools.tmf.ui.widgets.timegraph.ITimeGraphTimeListener; import fr.inria.linuxtools.tmf.ui.widgets.timegraph.TimeGraphCombo; import fr.inria.linuxtools.tmf.ui.widgets.timegraph.TimeGraphRangeUpdateEvent; import fr.inria.linuxtools.tmf.ui.widgets.timegraph.TimeGraphTimeEvent; import fr.inria.linuxtools.tmf.ui.widgets.timegraph.TimeGraphViewer; import fr.inria.linuxtools.tmf.ui.widgets.timegraph.model.ITimeEvent; import fr.inria.linuxtools.tmf.ui.widgets.timegraph.model.ITimeGraphEntry; import fr.inria.linuxtools.tmf.ui.widgets.timegraph.model.NullTimeEvent; import fr.inria.linuxtools.tmf.ui.widgets.timegraph.model.TimeEvent; import fr.inria.linuxtools.tmf.ui.widgets.timegraph.widgets.TimeGraphControl; import fr.inria.linuxtools.tmf.ui.widgets.timegraph.widgets.TimeGraphSelection; import fr.inria.linuxtools.tmf.ui.widgets.timegraph.widgets.Utils.TimeFormat; /** * Main implementation for the Call Stack view * * @author Patrick Tasse * @since 2.0 */ public class CallStackView extends TmfView { // ------------------------------------------------------------------------ // Constants // ------------------------------------------------------------------------ /** View ID. */ public static final String ID = "fr.inria.linuxtools.tmf.ui.views.callstack"; //$NON-NLS-1$ /** * Redraw state enum */ private enum State { IDLE, BUSY, PENDING } 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 int[] COLUMN_WIDTHS = new int[] { 200, 50, 120, 120, 120 }; // Fraction of a function duration to be added as spacing private static final double SPACING_RATIO = 0.01; 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_MAPPING_ICON_PATH = "icons/etool16/import.gif"; //$NON-NLS-1$ // ------------------------------------------------------------------------ // Fields // ------------------------------------------------------------------------ // The time graph combo private TimeGraphCombo fTimeGraphCombo; // The selected trace private ITmfTrace fTrace; // The selected thread map private final Map<ITmfTrace, String> fSelectedThreadMap = new HashMap<>(); // The time graph entry list private List<ThreadEntry> fEntryList; // The trace to entry list hash map private final Map<ITmfTrace, ArrayList<ThreadEntry>> fEntryListMap = new HashMap<>(); // The trace to build thread hash map private final Map<ITmfTrace, BuildThread> fBuildThreadMap = new HashMap<>(); /** The map to map function addresses to function names */ private Map<String, String> fNameMapping; // The start time private long fStartTime; // The end time private long fEndTime; // The display width private int fDisplayWidth; // 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 function-name mapping file */ private Action fImportMappingAction; // The zoom thread private ZoomThread fZoomThread; // The redraw state used to prevent unnecessary queuing of display runnables private State fRedrawState = State.IDLE; // The redraw synchronization object private final Object fSyncObj = new Object(); // The saved time sync. signal used when switching off the pinning of a view private TmfTimeSynchSignal fSavedTimeSyncSignal; // The saved time range sync. signal used when switching off the pinning of a view private TmfRangeSynchSignal fSavedRangeSyncSignal; // ------------------------------------------------------------------------ // Classes // ------------------------------------------------------------------------ private class ThreadEntry implements ITimeGraphEntry { // The start time private final long fTraceStartTime; // The end time private final long fTraceEndTime; // The children of the entry private ArrayList<CallStackEntry> fChildren; // The name of entry private final String fName; // The thread attribute quark private final int fThreadQuark; // The state system from which this entry comes private final ITmfStateSystem fSS; public ThreadEntry(ITmfTrace trace, String name, int threadQuark, long startTime, long endTime) { fChildren = new ArrayList<>(); fName = name; fTraceStartTime = startTime; fTraceEndTime = endTime; fThreadQuark = threadQuark; fSS = getCallStackStateSystem(trace); } @Override public ITimeGraphEntry getParent() { return null; } @Override public boolean hasChildren() { if (fChildren == null) { ITmfStateSystem ss = getStateSystem(); if (ss == null) { return false; } try { int eventStackQuark = ss.getQuarkRelative(fThreadQuark, CallStackStateProvider.CALL_STACK); ITmfStateInterval eventStackInterval = ss.querySingleState(ss.getStartTime(), eventStackQuark); return ! eventStackInterval.getStateValue().isNull() || eventStackInterval.getEndTime() != ss.getCurrentEndTime(); } 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 fChildren != null && fChildren.size() > 0; } @Override public List<CallStackEntry> getChildren() { return fChildren; } @Override public String getName() { return fName; } @Override public long getStartTime() { return fTraceStartTime; } @Override public long getEndTime() { return fTraceEndTime; } @Override public boolean hasTimeEvents() { return false; } @Override public Iterator<ITimeEvent> getTimeEventsIterator() { return null; } @Override public <T extends ITimeEvent> Iterator<T> getTimeEventsIterator(long startTime, long stopTime, long visibleDuration) { return null; } public int getThreadQuark() { return fThreadQuark; } @Nullable public ITmfStateSystem getStateSystem() { return fSS; } public void addChild(CallStackEntry entry) { entry.setParent(this); fChildren.add(entry); } } private class TreeContentProvider implements ITreeContentProvider { @Override public void dispose() { } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } @Override public Object[] getElements(Object inputElement) { return (ITimeGraphEntry[]) inputElement; } @Override public Object[] getChildren(Object parentElement) { ITimeGraphEntry entry = (ITimeGraphEntry) parentElement; return entry.getChildren().toArray(); } @Override public Object getParent(Object element) { ITimeGraphEntry entry = (ITimeGraphEntry) element; return entry.getParent(); } @Override public boolean hasChildren(Object element) { ITimeGraphEntry entry = (ITimeGraphEntry) element; return entry.hasChildren(); } } private class TreeLabelProvider implements ITableLabelProvider { @Override public void addListener(ILabelProviderListener listener) { } @Override public void dispose() { } @Override public boolean isLabelProperty(Object element, String property) { return false; } @Override public void removeListener(ILabelProviderListener listener) { } @Override public Image getColumnImage(Object element, int columnIndex) { if (columnIndex == 0) { 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 ThreadEntry) { if (columnIndex == 0) { return ((ThreadEntry) element).getName(); } } else 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 = new TmfTimestamp(entry.getStartTime(), ITmfTimestamp.NANOSECOND_SCALE); return ts.toString(); } else if (columnIndex == 3 && entry.getFunctionName().length() > 0) { ITmfTimestamp ts = new TmfTimestamp(entry.getEndTime(), ITmfTimestamp.NANOSECOND_SCALE); return ts.toString(); } else if (columnIndex == 4 && entry.getFunctionName().length() > 0) { ITmfTimestamp ts = new TmfTimestampDelta(entry.getEndTime() - entry.getStartTime(), ITmfTimestamp.NANOSECOND_SCALE); return ts.toString(); } } return ""; //$NON-NLS-1$ } } private class BuildThread extends Thread { private final ITmfTrace fBuildTrace; private final IProgressMonitor fMonitor; public BuildThread(ITmfTrace trace) { super("CallStackView build"); //$NON-NLS-1$ fBuildTrace = trace; fMonitor = new NullProgressMonitor(); } @Override public void run() { buildThreadList(fBuildTrace, fMonitor); synchronized (fBuildThreadMap) { fBuildThreadMap.remove(this); } } public void cancel() { fMonitor.setCanceled(true); } } private class ZoomThread extends Thread { private final List<ThreadEntry> fZoomEntryList; private final long fZoomStartTime; private final long fZoomEndTime; private final IProgressMonitor fMonitor; public ZoomThread(List<ThreadEntry> entryList, long startTime, long endTime) { super("ResourcesView zoom"); //$NON-NLS-1$ fZoomEntryList = entryList; fZoomStartTime = startTime; fZoomEndTime = endTime; fMonitor = new NullProgressMonitor(); } @Override public void run() { if (fZoomEntryList == null) { return; } long resolution = Math.max(1, (fZoomEndTime - fZoomStartTime) / fDisplayWidth); for (ThreadEntry threadEntry : fZoomEntryList) { ITmfStateSystem ss = threadEntry.getStateSystem(); if (ss == null) { continue; } ss.waitUntilBuilt(); if (ss.isCancelled()) { continue; } for (ITimeGraphEntry child : threadEntry.getChildren()) { if (fMonitor.isCanceled()) { break; } CallStackEntry entry = (CallStackEntry) child; if (fZoomStartTime <= fStartTime && fZoomEndTime >= fEndTime) { entry.setZoomedEventList(null); } else { List<ITimeEvent> zoomedEventList = getEventList(entry, fZoomStartTime, fZoomEndTime, resolution, fMonitor); if (zoomedEventList != null) { entry.setZoomedEventList(zoomedEventList); } } redraw(); } } } public void cancel() { fMonitor.setCanceled(true); } } // ------------------------------------------------------------------------ // Constructors // ------------------------------------------------------------------------ /** * Default constructor */ public CallStackView() { super(ID); fDisplayWidth = Display.getDefault().getBounds().width; } // ------------------------------------------------------------------------ // ViewPart // ------------------------------------------------------------------------ @Override public void createPartControl(Composite parent) { fTimeGraphCombo = new TimeGraphCombo(parent, SWT.NONE); fTimeGraphCombo.setTreeContentProvider(new TreeContentProvider()); fTimeGraphCombo.setTreeLabelProvider(new TreeLabelProvider()); fTimeGraphCombo.setTreeColumns(COLUMN_NAMES); fTimeGraphCombo.getTreeViewer().getTree().getColumn(0).setWidth(COLUMN_WIDTHS[0]); fTimeGraphCombo.getTreeViewer().getTree().getColumn(1).setWidth(COLUMN_WIDTHS[1]); fTimeGraphCombo.getTreeViewer().getTree().getColumn(2).setWidth(COLUMN_WIDTHS[2]); fTimeGraphCombo.getTreeViewer().getTree().getColumn(3).setWidth(COLUMN_WIDTHS[3]); fTimeGraphCombo.getTreeViewer().getTree().getColumn(4).setWidth(COLUMN_WIDTHS[4]); fTimeGraphCombo.setTimeGraphProvider(new CallStackPresentationProvider(this)); fTimeGraphCombo.getTimeGraphViewer().setTimeFormat(TimeFormat.CALENDAR); fTimeGraphCombo.getTimeGraphViewer().addRangeListener(new ITimeGraphRangeListener() { @Override public void timeRangeUpdated(TimeGraphRangeUpdateEvent event) { long startTime = event.getStartTime(); long endTime = event.getEndTime(); TmfTimeRange range = new TmfTimeRange(new TmfNanoTimestamp(startTime), new TmfNanoTimestamp(endTime)); broadcast(new TmfRangeSynchSignal(CallStackView.this, range)); startZoomThread(startTime, endTime); } }); fTimeGraphCombo.getTimeGraphViewer().addTimeListener(new ITimeGraphTimeListener() { @Override public void timeSelected(TimeGraphTimeEvent event) { long beginTime = event.getBeginTime(); long endTime = event.getEndTime(); selectTime(beginTime); broadcast(new TmfTimeSynchSignal(CallStackView.this, new TmfNanoTimestamp(beginTime), new TmfNanoTimestamp(endTime))); } }); fTimeGraphCombo.getTimeGraphViewer().getControl().addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { fDisplayWidth = fTimeGraphCombo.getTimeGraphViewer().getControl().getSize().x; if (fEntryList != null) { startZoomThread(fTimeGraphCombo.getTimeGraphViewer().getTime0(), fTimeGraphCombo.getTimeGraphViewer().getTime1()); } } }); fTimeGraphCombo.getTreeViewer().addDoubleClickListener(new IDoubleClickListener() { @Override public void doubleClick(DoubleClickEvent event) { Object selection = ((IStructuredSelection) event.getSelection()).getFirstElement(); if (selection instanceof CallStackEntry) { CallStackEntry entry = (CallStackEntry) selection; if (entry.getFunctionName().length() > 0) { long startTime = entry.getStartTime(); long endTime = entry.getEndTime(); long spacingTime = (long) ((endTime - startTime) * SPACING_RATIO); startTime -= spacingTime; endTime += spacingTime; TmfTimeRange range = new TmfTimeRange(new TmfNanoTimestamp(startTime), new TmfNanoTimestamp(endTime)); broadcast(new TmfRangeSynchSignal(CallStackView.this, range)); fTimeGraphCombo.getTimeGraphViewer().setStartFinishTime(startTime, endTime); startZoomThread(startTime, endTime); } } } }); fTimeGraphCombo.getTimeGraphViewer().getTimeGraphControl().addMouseListener(new MouseAdapter() { @Override public void mouseDoubleClick(MouseEvent e) { TimeGraphControl timeGraphControl = fTimeGraphCombo.getTimeGraphViewer().getTimeGraphControl(); ISelection selection = timeGraphControl.getSelection(); if (selection instanceof TimeGraphSelection) { Object o = ((TimeGraphSelection) selection).getFirstElement(); if (o instanceof CallStackEvent) { CallStackEvent event = (CallStackEvent) o; long startTime = event.getTime(); long endTime = startTime + event.getDuration(); long spacingTime = (long) ((endTime - startTime) * SPACING_RATIO); startTime -= spacingTime; endTime += spacingTime; TmfTimeRange range = new TmfTimeRange(new TmfNanoTimestamp(startTime), new TmfNanoTimestamp(endTime)); broadcast(new TmfRangeSynchSignal(CallStackView.this, range)); fTimeGraphCombo.getTimeGraphViewer().setStartFinishTime(startTime, endTime); startZoomThread(startTime, endTime); } } } }); IStatusLineManager statusLineManager = getViewSite().getActionBars().getStatusLineManager(); fTimeGraphCombo.getTimeGraphViewer().getTimeGraphControl().setStatusLineManager(statusLineManager); // View Action Handling makeActions(); contributeToActionBars(); IEditorPart editor = getSite().getPage().getActiveEditor(); if (editor instanceof ITmfTraceEditor) { ITmfTrace trace = ((ITmfTraceEditor) editor).getTrace(); if (trace != null) { traceSelected(new TmfTraceSelectedSignal(this, trace)); } } } @Override public void setFocus() { fTimeGraphCombo.setFocus(); } // ------------------------------------------------------------------------ // Signal handlers // ------------------------------------------------------------------------ /** * Handler for the trace opened signal. * @param signal * The incoming signal * @since 2.0 */ @TmfSignalHandler public void traceOpened(TmfTraceOpenedSignal signal) { fTrace = signal.getTrace(); loadTrace(); } /** * Handler for the trace selected signal * * @param signal * The incoming signal */ @TmfSignalHandler public void traceSelected(final TmfTraceSelectedSignal signal) { if (signal.getTrace() == fTrace) { return; } fTrace = signal.getTrace(); loadTrace(); } /** * Trace is closed: clear the data structures and the view * * @param signal the signal received */ @TmfSignalHandler public void traceClosed(final TmfTraceClosedSignal signal) { synchronized (fBuildThreadMap) { BuildThread buildThread = fBuildThreadMap.remove(signal.getTrace()); if (buildThread != null) { buildThread.cancel(); } } synchronized (fEntryListMap) { fEntryListMap.remove(signal.getTrace()); } fSelectedThreadMap.remove(signal.getTrace()); if (signal.getTrace() == fTrace) { fTrace = null; fStartTime = 0; fEndTime = 0; refresh(); } } /** * Handler for the TimeSynch signal * * @param signal * The incoming signal */ @TmfSignalHandler public void synchToTime(final TmfTimeSynchSignal signal) { fSavedTimeSyncSignal = isPinned() ? new TmfTimeSynchSignal(signal.getSource(), signal.getBeginTime(), signal.getEndTime()) : null; if (signal.getSource() == this || fTrace == null || isPinned()) { return; } final long beginTime = signal.getBeginTime().normalize(0, ITmfTimestamp.NANOSECOND_SCALE).getValue(); final long endTime = signal.getEndTime().normalize(0, ITmfTimestamp.NANOSECOND_SCALE).getValue(); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (fTimeGraphCombo.isDisposed()) { return; } if (beginTime == endTime) { fTimeGraphCombo.getTimeGraphViewer().setSelectedTime(beginTime, true); } else { fTimeGraphCombo.getTimeGraphViewer().setSelectionRange(beginTime, endTime); } selectTime(beginTime); startZoomThread(fTimeGraphCombo.getTimeGraphViewer().getTime0(), fTimeGraphCombo.getTimeGraphViewer().getTime1()); if (fEntryList == null) { return; } TimeGraphViewer viewer = fTimeGraphCombo.getTimeGraphViewer(); for (ThreadEntry threadEntry : fEntryList) { ITmfStateSystem ss = threadEntry.getStateSystem(); if (ss == null || beginTime < ss.getStartTime() || beginTime > ss.getCurrentEndTime()) { continue; } try { int quark = ss.getQuarkRelative(threadEntry.getThreadQuark(), CallStackStateProvider.CALL_STACK); ITmfStateInterval stackInterval = ss.querySingleState(beginTime, quark); if (beginTime == stackInterval.getStartTime()) { int stackLevel = stackInterval.getStateValue().unboxInt(); CallStackEntry selectedEntry = threadEntry.getChildren().get(Math.max(0, stackLevel - 1)); fTimeGraphCombo.setSelection(selectedEntry); viewer.getTimeGraphControl().fireSelectionChanged(); break; } } 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) { Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$ } catch (StateValueTypeException e) { Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$ } } } }); } /** * Handler for the RangeSynch signal * * @param signal * The incoming signal */ @TmfSignalHandler public void synchToRange(final TmfRangeSynchSignal signal) { if (isPinned()) { fSavedRangeSyncSignal = new TmfRangeSynchSignal(signal.getSource(), new TmfTimeRange(signal.getCurrentRange().getStartTime(), signal.getCurrentRange().getEndTime())); fSavedTimeSyncSignal = null; } if (signal.getSource() == this || fTrace == null || isPinned()) { return; } if (signal.getCurrentRange().getIntersection(fTrace.getTimeRange()) == null) { return; } final long startTime = signal.getCurrentRange().getStartTime().normalize(0, ITmfTimestamp.NANOSECOND_SCALE).getValue(); final long endTime = signal.getCurrentRange().getEndTime().normalize(0, ITmfTimestamp.NANOSECOND_SCALE).getValue(); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (fTimeGraphCombo.isDisposed()) { return; } fTimeGraphCombo.getTimeGraphViewer().setStartFinishTime(startTime, endTime); startZoomThread(startTime, endTime); } }); } // ------------------------------------------------------------------------ // Internal // ------------------------------------------------------------------------ private void loadTrace() { synchronized (fEntryListMap) { fEntryList = fEntryListMap.get(fTrace); if (fEntryList == null) { synchronized (fBuildThreadMap) { BuildThread buildThread = new BuildThread(fTrace); fBuildThreadMap.put(fTrace, buildThread); buildThread.start(); } } else { fStartTime = fTrace.getStartTime().normalize(0, ITmfTimestamp.NANOSECOND_SCALE).getValue(); fEndTime = fTrace.getEndTime().normalize(0, ITmfTimestamp.NANOSECOND_SCALE).getValue(); refresh(); } } } private void buildThreadList(final ITmfTrace trace, IProgressMonitor monitor) { fStartTime = Long.MAX_VALUE; fEndTime = Long.MIN_VALUE; ITmfTrace[] traces = TmfTraceManager.getTraceSet(trace); ArrayList<ThreadEntry> entryList = new ArrayList<>(); for (ITmfTrace aTrace : traces) { if (monitor.isCanceled()) { return; } ITmfStateSystem ss = getCallStackStateSystem(aTrace); if (ss == null) { addUnavailableEntry(aTrace, entryList); continue; } ss.waitUntilBuilt(); if (ss.isCancelled()) { addUnavailableEntry(aTrace, entryList); continue; } long startTime = ss.getStartTime(); long endTime = ss.getCurrentEndTime() + 1; fStartTime = Math.min(fStartTime, startTime); fEndTime = Math.max(fEndTime, endTime); List<Integer> threadQuarks = ss.getQuarks(CallStackStateProvider.THREADS, "*"); //$NON-NLS-1$ for (int i = 0; i < threadQuarks.size(); i++) { if (monitor.isCanceled()) { return; } int threadQuark = threadQuarks.get(i); String thread = ss.getAttributeName(threadQuark); String threadEntryName = thread + ' ' + '(' + aTrace.getName() + ')'; ThreadEntry threadEntry = new ThreadEntry(aTrace, threadEntryName, threadQuark, startTime, endTime); entryList.add(threadEntry); int eventStackQuark; try { eventStackQuark = ss.getQuarkRelative(threadQuark, CallStackStateProvider.CALL_STACK); int level = 1; for (int stackLevelQuark : ss.getSubAttributes(eventStackQuark, false)) { CallStackEntry callStackEntry = new CallStackEntry(stackLevelQuark, level++, aTrace); threadEntry.addChild(callStackEntry); } } catch (AttributeNotFoundException e) { Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$ } } } synchronized (fEntryListMap) { fEntryListMap.put(trace, new ArrayList<>(entryList)); } if (trace == fTrace) { refresh(); } for (ThreadEntry threadEntry : entryList) { for (CallStackEntry callStackEntry : threadEntry.getChildren()) { if (monitor.isCanceled()) { return; } buildStatusEvents(trace, callStackEntry, monitor); } } } private void addUnavailableEntry(ITmfTrace trace, List<ThreadEntry> list) { String threadName = Messages.CallStackView_StackInfoNotAvailable + ' ' + '(' + trace.getName() + ')'; ThreadEntry threadEntry = new ThreadEntry(trace, threadName, -1, 0, 0); list.add(threadEntry); } private void buildStatusEvents(ITmfTrace trace, CallStackEntry entry, IProgressMonitor monitor) { ITmfStateSystem ss = getCallStackStateSystem(entry.getTrace()); if (ss == null) { return; } long start = ss.getStartTime(); long end = ss.getCurrentEndTime() + 1; long resolution = Math.max(1, (end - start) / fDisplayWidth); List<ITimeEvent> eventList = getEventList(entry, start, end, resolution, monitor); if (monitor.isCanceled()) { return; } entry.setEventList(eventList); if (trace == fTrace) { redraw(); } } private static List<ITimeEvent> getEventList(CallStackEntry entry, long startTime, long endTime, long resolution, IProgressMonitor monitor) { ITmfStateSystem ss = getCallStackStateSystem(entry.getTrace()); if (ss == null) { return null; } long start = Math.max(startTime, ss.getStartTime()); long end = Math.min(endTime, ss.getCurrentEndTime() + 1); if (end <= start) { return null; } List<ITimeEvent> eventList = null; try { List<ITmfStateInterval> stackIntervals = ss.queryHistoryRange(entry.getQuark(), start, end - 1, resolution, monitor); eventList = new ArrayList<>(stackIntervals.size()); long lastEndTime = -1; boolean lastIsNull = true; 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) { // 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) { // 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; } private void selectTime(long time) { if (fEntryList == null) { return; } for (ThreadEntry threadEntry : fEntryList) { ITmfStateSystem ss = threadEntry.getStateSystem(); if (ss == null) { continue; } ss.waitUntilBuilt(); if (ss.isCancelled()) { continue; } long queryTime = Math.max(ss.getStartTime(), Math.min(ss.getCurrentEndTime(), time)); for (CallStackEntry callStackEntry : threadEntry.getChildren()) { try { ITmfStateInterval stackLevelInterval = ss.querySingleState(queryTime, callStackEntry.getQuark()); ITmfStateValue nameValue = stackLevelInterval.getStateValue(); String name = ""; //$NON-NLS-1$ try { if (nameValue.getType() == Type.STRING) { String address = nameValue.unboxStr(); name = getFunctionName(address); } else if (nameValue.getType() == Type.INTEGER) { name = "0x" + Integer.toHexString(nameValue.unboxInt()); //$NON-NLS-1$ } else if (nameValue.getType() == Type.LONG) { name = "0x" + Long.toHexString(nameValue.unboxLong()); //$NON-NLS-1$ } } catch (StateValueTypeException e) { } callStackEntry.setFunctionName(name); if (name.length() > 0) { callStackEntry.setStartTime(stackLevelInterval.getStartTime()); callStackEntry.setEndTime(stackLevelInterval.getEndTime() + 1); } } 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) { Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$ } } } fTimeGraphCombo.refresh(); } private void refresh() { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (fTimeGraphCombo.isDisposed()) { return; } ITimeGraphEntry[] entries = null; synchronized (fEntryListMap) { fEntryList = fEntryListMap.get(fTrace); if (fEntryList == null) { fEntryList = new ArrayList<>(); } entries = fEntryList.toArray(new ITimeGraphEntry[0]); } fTimeGraphCombo.setInput(entries); fTimeGraphCombo.getTimeGraphViewer().setTimeBounds(fStartTime, fEndTime); long selectionBeginTime = fTrace == null ? 0 : fTraceManager.getSelectionBeginTime().normalize(0, ITmfTimestamp.NANOSECOND_SCALE).getValue(); long selectionEndTime = fTrace == null ? 0 : fTraceManager.getSelectionEndTime().normalize(0, ITmfTimestamp.NANOSECOND_SCALE).getValue(); long startTime = fTrace == null ? 0 : fTraceManager.getCurrentRange().getStartTime().normalize(0, ITmfTimestamp.NANOSECOND_SCALE).getValue(); long endTime = fTrace == null ? 0 : fTraceManager.getCurrentRange().getEndTime().normalize(0, ITmfTimestamp.NANOSECOND_SCALE).getValue(); startTime = Math.max(startTime, fStartTime); endTime = Math.min(endTime, fEndTime); fTimeGraphCombo.getTimeGraphViewer().setSelectionRange(selectionBeginTime, selectionEndTime); selectTime(selectionBeginTime); fTimeGraphCombo.getTimeGraphViewer().setStartFinishTime(startTime, endTime); startZoomThread(startTime, endTime); } }); } private void redraw() { synchronized (fSyncObj) { if (fRedrawState == State.IDLE) { fRedrawState = State.BUSY; } else { fRedrawState = State.PENDING; return; } } Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (fTimeGraphCombo.isDisposed()) { return; } fTimeGraphCombo.redraw(); fTimeGraphCombo.update(); synchronized (fSyncObj) { if (fRedrawState == State.PENDING) { fRedrawState = State.IDLE; redraw(); } else { fRedrawState = State.IDLE; } } } }); } private void startZoomThread(long startTime, long endTime) { if (fZoomThread != null) { fZoomThread.cancel(); } fZoomThread = new ZoomThread(fEntryList, startTime, endTime); fZoomThread.start(); } private void makeActions() { fPreviousItemAction = fTimeGraphCombo.getTimeGraphViewer().getPreviousItemAction(); fPreviousItemAction.setText(Messages.TmfTimeGraphViewer_PreviousItemActionNameText); fPreviousItemAction.setToolTipText(Messages.TmfTimeGraphViewer_PreviousItemActionToolTipText); fNextItemAction = fTimeGraphCombo.getTimeGraphViewer().getNextItemAction(); fNextItemAction.setText(Messages.TmfTimeGraphViewer_NextItemActionNameText); fNextItemAction.setToolTipText(Messages.TmfTimeGraphViewer_NextItemActionToolTipText); } private void contributeToActionBars() { IActionBars bars = getViewSite().getActionBars(); fillLocalToolBar(bars.getToolBarManager()); // Create pin action contributePinActionToToolBar(); fPinAction.addPropertyChangeListener(new IPropertyChangeListener(){ @Override public void propertyChange(PropertyChangeEvent event) { if (IAction.CHECKED.equals(event.getProperty()) && !isPinned()) { if (fSavedRangeSyncSignal != null) { synchToRange(fSavedRangeSyncSignal); fSavedRangeSyncSignal = null; } if (fSavedTimeSyncSignal != null) { synchToTime(fSavedTimeSyncSignal); fSavedTimeSyncSignal = null; } } } }); } private void fillLocalToolBar(IToolBarManager manager) { manager.add(getImportMappingAction()); manager.add(fTimeGraphCombo.getTimeGraphViewer().getResetScaleAction()); manager.add(getPreviousEventAction()); manager.add(getNextEventAction()); manager.add(fPreviousItemAction); manager.add(fNextItemAction); manager.add(fTimeGraphCombo.getTimeGraphViewer().getZoomInAction()); manager.add(fTimeGraphCombo.getTimeGraphViewer().getZoomOutAction()); manager.add(new Separator()); } /** * 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 = fTimeGraphCombo.getTimeGraphViewer(); ITimeGraphEntry entry = viewer.getSelection(); if (entry instanceof CallStackEntry) { try { CallStackEntry callStackEntry = (CallStackEntry) entry; ITmfTrace trace = callStackEntry.getTrace(); ITmfStateSystem ss = getCallStackStateSystem(trace); if (ss == null) { return; } long time = Math.max(ss.getStartTime(), Math.min(ss.getCurrentEndTime(), viewer.getSelectionBegin())); ThreadEntry threadEntry = (ThreadEntry) callStackEntry.getParent(); int quark = ss.getQuarkRelative(threadEntry.getThreadQuark(), CallStackStateProvider.CALL_STACK); 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(); CallStackEntry selectedEntry = threadEntry.getChildren().get(Math.max(0, stackLevel - 1)); fTimeGraphCombo.setSelection(selectedEntry); viewer.getTimeGraphControl().fireSelectionChanged(); startZoomThread(viewer.getTime0(), viewer.getTime1()); } 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) { Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$ } catch (StateValueTypeException e) { Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$ } } } }; fNextEventAction.setText(Messages.TmfTimeGraphViewer_NextEventActionNameText); fNextEventAction.setToolTipText(Messages.TmfTimeGraphViewer_NextEventActionToolTipText); fNextEventAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_NEXT_EVENT)); } 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 = fTimeGraphCombo.getTimeGraphViewer(); ITimeGraphEntry entry = viewer.getSelection(); if (entry instanceof CallStackEntry) { try { CallStackEntry callStackEntry = (CallStackEntry) entry; ITmfTrace trace = callStackEntry.getTrace(); ITmfStateSystem ss = getCallStackStateSystem(trace); if (ss == null) { return; } long time = Math.max(ss.getStartTime(), Math.min(ss.getCurrentEndTime(), viewer.getSelectionBegin())); ThreadEntry threadEntry = (ThreadEntry) callStackEntry.getParent(); int quark = ss.getQuarkRelative(threadEntry.getThreadQuark(), CallStackStateProvider.CALL_STACK); 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(); CallStackEntry selectedEntry = threadEntry.getChildren().get(Math.max(0, stackLevel - 1)); fTimeGraphCombo.setSelection(selectedEntry); viewer.getTimeGraphControl().fireSelectionChanged(); startZoomThread(viewer.getTime0(), viewer.getTime1()); } 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) { Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$ } catch (StateValueTypeException e) { Activator.getDefault().logError("Error querying state system", e); //$NON-NLS-1$ } } } }; fPrevEventAction.setText(Messages.TmfTimeGraphViewer_PreviousEventActionNameText); fPrevEventAction.setToolTipText(Messages.TmfTimeGraphViewer_PreviousEventActionToolTipText); fPrevEventAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath(ITmfImageConstants.IMG_UI_PREV_EVENT)); } return fPrevEventAction; } @Nullable static ITmfStateSystem getCallStackStateSystem(ITmfTrace trace) { /* * Since we cannot know the exact analysis ID (in separate plugins), we * will search using the analysis type. */ Iterable<AbstractCallStackAnalysis> modules = trace.getAnalysisModulesOfClass(AbstractCallStackAnalysis.class); Iterator<AbstractCallStackAnalysis> 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. */ AbstractCallStackAnalysis module = it.next(); /* This analysis is not automatic, we need to schedule it on-demand */ module.schedule(); module.waitForInitialization(); ITmfStateSystem ss = module.getStateSystem(); if (ss == null) { /* If we've waited for initialization, 'ss' should not be null */ throw new IllegalStateException(); } return ss; } // ------------------------------------------------------------------------ // Methods related to function name mapping // ------------------------------------------------------------------------ /** * Toolbar icon to import the function address-to-name mapping file. */ private Action getImportMappingAction() { if (fImportMappingAction != null) { return fImportMappingAction; } fImportMappingAction = new Action() { @Override public void run() { FileDialog dialog = new FileDialog(getViewSite().getShell()); dialog.setText(Messages.CallStackView_ImportMappingDialogTitle); String filePath = dialog.open(); if (filePath == null) { /* No file was selected, don't change anything */ return; } /* * Start the mapping import in a separate thread (we do not want * to UI thread to do this). */ Job job = new ImportMappingJob(new File(filePath)); job.schedule(); } }; fImportMappingAction.setText(Messages.CallStackView_ImportMappingButtonText); fImportMappingAction.setToolTipText(Messages.CallStackView_ImportMappingButtonTooltip); fImportMappingAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath(IMPORT_MAPPING_ICON_PATH)); return fImportMappingAction; } private class ImportMappingJob extends Job { private final File fMappingFile; public ImportMappingJob(File mappingFile) { super(Messages.CallStackView_ImportMappingJobName); fMappingFile = mappingFile; } @Override public IStatus run(IProgressMonitor monitor) { fNameMapping = FunctionNameMapper.mapFromNmTextFile(fMappingFile); /* Refresh the time graph and the list of entries */ buildThreadList(fTrace, new NullProgressMonitor()); redraw(); return Status.OK_STATUS; } } String getFunctionName(String address) { if (fNameMapping == null) { /* No mapping available, just print the addresses */ return address; } String ret = fNameMapping.get(address); if (ret == null) { /* We didn't find this address in the mapping file, just use the address */ return address; } return ret; } }