timing.ui: Remove dependency on trace with FlameGraphContentProvider
[deliverable/tracecompass.git] / analysis / org.eclipse.tracecompass.analysis.timing.ui / src / org / eclipse / tracecompass / internal / analysis / timing / ui / flamegraph / FlameGraphView.java
index bed3e4af17cfb108e817bad8306cbe097dd16117..309f06f25dc5a505d0c1200ac22e631e5a848f5c 100644 (file)
  *******************************************************************************/
 package org.eclipse.tracecompass.internal.analysis.timing.ui.flamegraph;
 
+import java.util.concurrent.Semaphore;
+
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.GroupMarker;
+import org.eclipse.jface.action.IAction;
+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.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.widgets.Composite;
 import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Menu;
 import org.eclipse.tracecompass.internal.analysis.timing.core.callgraph.CallGraphAnalysis;
+import org.eclipse.tracecompass.internal.analysis.timing.ui.Activator;
 import org.eclipse.tracecompass.internal.analysis.timing.ui.callgraph.CallGraphAnalysisUI;
+import org.eclipse.tracecompass.segmentstore.core.ISegment;
+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.TmfTraceOpenedSignal;
 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
 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.views.TmfView;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphPresentationProvider;
 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphViewer;
+import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphControl;
+import org.eclipse.ui.IActionBars;
 import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchActionConstants;
+
+import com.google.common.annotations.VisibleForTesting;
 
 /**
  * View to display the flame graph .This uses the flameGraphNode tree generated
@@ -45,6 +73,12 @@ public class FlameGraphView extends TmfView {
      */
     public static final String ID = FlameGraphView.class.getPackage().getName() + ".flamegraphView"; //$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 TimeGraphViewer fTimeGraphViewer;
 
     private FlameGraphContentProvider fTimeGraphContentProvider;
@@ -53,6 +87,15 @@ public class FlameGraphView extends TmfView {
 
     private ITmfTrace fTrace;
 
+    private final @NonNull MenuManager fEventMenuManager = new MenuManager();
+    private Action fSortByNameAction;
+    private Action fSortByIdAction;
+    /**
+     * A plain old semaphore is used since different threads will be competing
+     * for the same resource.
+     */
+    private final Semaphore fLock = new Semaphore(1);
+
     /**
      * Constructor
      */
@@ -75,21 +118,39 @@ public class FlameGraphView extends TmfView {
                 traceSelected(new TmfTraceSelectedSignal(this, trace));
             }
         }
+        contributeToActionBars();
+        loadSortOption();
+
+        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 FlamegraphEvent) {
+                            FlamegraphEvent event = (FlamegraphEvent) object;
+                            long startTime = event.getTime();
+                            long endTime = startTime + event.getDuration();
+                            getTimeGraphViewer().setStartFinishTime(startTime, endTime);
+                            break;
+                        }
+                    }
+                }
+            }
+        });
     }
 
     /**
-     * Handler for the trace opened signal
+     * Get the time graph viewer
      *
-     * @param signal
-     *            The incoming signal
+     * @return the time graph viewer
      */
-    @TmfSignalHandler
-    public void TraceOpened(TmfTraceOpenedSignal signal) {
-        fTrace = signal.getTrace();
-        if (fTrace != null) {
-            CallGraphAnalysis flamegraphModule = TmfTraceUtils.getAnalysisModuleOfClass(fTrace, CallGraphAnalysis.class, CallGraphAnalysisUI.ID);
-            buildFlameGraph(flamegraphModule);
-        }
+    @VisibleForTesting
+    public TimeGraphViewer getTimeGraphViewer() {
+        return fTimeGraphViewer;
     }
 
     /**
@@ -110,22 +171,50 @@ public class FlameGraphView extends TmfView {
     /**
      * Get the necessary data for the flame graph and display it
      *
-     * @param flamegraphModule
+     * @param callGraphAnalysis
      *            the callGraphAnalysis
      */
-    private void buildFlameGraph(CallGraphAnalysis callGraphAnalysis) {
-        fTimeGraphViewer.setInput(null);
+    @VisibleForTesting
+    public void buildFlameGraph(CallGraphAnalysis callGraphAnalysis) {
+        /*
+         * 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- if the request is cancelled before it gets to the display
+         *
+         * 4- on a clean execution
+         */
+        try {
+            fLock.acquire();
+        } catch (InterruptedException e) {
+            Activator.getDefault().logError(e.getMessage(), e);
+            fLock.release();
+        }
+        if (callGraphAnalysis == null) {
+            fTimeGraphViewer.setInput(null);
+            fLock.release();
+            return;
+        }
+        fTimeGraphViewer.setInput(callGraphAnalysis.getSegmentStore());
         callGraphAnalysis.schedule();
         Job j = new Job(Messages.CallGraphAnalysis_Execution) {
 
             @Override
             protected IStatus run(IProgressMonitor monitor) {
                 if (monitor.isCanceled()) {
+                    fLock.release();
                     return Status.CANCEL_STATUS;
                 }
                 callGraphAnalysis.waitForCompletion(monitor);
                 Display.getDefault().asyncExec(() -> {
                     fTimeGraphViewer.setInput(callGraphAnalysis.getThreadNodes());
+                    fTimeGraphViewer.resetStartFinishTime();
+                    fLock.release();
                 });
                 return Status.OK_STATUS;
             }
@@ -133,6 +222,21 @@ public class FlameGraphView extends TmfView {
         j.schedule();
     }
 
+    /**
+     * Await the next refresh
+     *
+     * @throws InterruptedException
+     *             something took too long
+     */
+    @VisibleForTesting
+    public void waitForUpdate() throws InterruptedException {
+        /*
+         * wait for the semaphore to be available, then release it immediately
+         */
+        fLock.acquire();
+        fLock.release();
+    }
+
     /**
      * Trace is closed: clear the data structures and the view
      *
@@ -151,4 +255,181 @@ public class FlameGraphView extends TmfView {
         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 FlamegraphEvent) {
+                    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 FlamegraphEvent) {
+                    final FlamegraphEvent flamegraphEvent = (FlamegraphEvent) object;
+                    menuManager.add(new Action(Messages.FlameGraphView_GotoMaxDuration) {
+                        @Override
+                        public void run() {
+                            ISegment maxSeg = flamegraphEvent.getStatistics().getMaxSegment();
+                            TmfSelectionRangeUpdatedSignal sig = new TmfSelectionRangeUpdatedSignal(this, TmfTimestamp.fromNanos(maxSeg.getStart()), TmfTimestamp.fromNanos(maxSeg.getEnd()));
+                            broadcast(sig);
+                        }
+                    });
+
+                    menuManager.add(new Action(Messages.FlameGraphView_GotoMinDuration) {
+                        @Override
+                        public void run() {
+                            ISegment minSeg = flamegraphEvent.getStatistics().getMinSegment();
+                            TmfSelectionRangeUpdatedSignal sig = new TmfSelectionRangeUpdatedSignal(this, TmfTimestamp.fromNanos(minSeg.getStart()), TmfTimestamp.fromNanos(minSeg.getEnd()));
+                            broadcast(sig);
+                        }
+                    });
+                }
+            }
+        }
+    }
+
+    private void contributeToActionBars() {
+        IActionBars bars = getViewSite().getActionBars();
+        fillLocalToolBar(bars.getToolBarManager());
+    }
+
+    private void fillLocalToolBar(IToolBarManager manager) {
+        manager.add(getSortByNameAction());
+        manager.add(getSortByIdAction());
+        manager.add(new Separator());
+    }
+
+    private Action getSortByNameAction() {
+        if (fSortByNameAction == null) {
+            fSortByNameAction = new Action(Messages.FlameGraph_SortByThreadName, IAction.AS_CHECK_BOX) {
+                @Override
+                public void run() {
+                    SortOption sortOption = fTimeGraphContentProvider.getSortOption();
+                    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 = fTimeGraphContentProvider.getSortOption();
+                    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)) {
+            fTimeGraphContentProvider.setSortOption(SortOption.BY_NAME);
+            getSortByNameAction().setChecked(true);
+        } else if (sortOption.equals(SortOption.BY_NAME_REV)) {
+            fTimeGraphContentProvider.setSortOption(SortOption.BY_NAME_REV);
+            getSortByNameAction().setChecked(true);
+            getSortByNameAction().setImageDescriptor(SORT_BY_NAME_REV_ICON);
+        } else if (sortOption.equals(SortOption.BY_ID)) {
+            fTimeGraphContentProvider.setSortOption(SortOption.BY_ID);
+            getSortByIdAction().setChecked(true);
+        } else if (sortOption.equals(SortOption.BY_ID_REV)) {
+            fTimeGraphContentProvider.setSortOption(SortOption.BY_ID_REV);
+            getSortByIdAction().setChecked(true);
+            getSortByIdAction().setImageDescriptor(SORT_BY_ID_REV_ICON);
+        }
+        saveSortOption();
+        fTimeGraphViewer.refresh();
+    }
+
+    private void saveSortOption() {
+        SortOption sortOption = fTimeGraphContentProvider.getSortOption();
+        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));
+    }
+
 }
This page took 0.028843 seconds and 5 git commands to generate.