/*******************************************************************************
* Copyright (c) 2016 Ericsson
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Author:
* Sonia Farrah
*******************************************************************************/
package org.eclipse.tracecompass.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.TmfSignalManager;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
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.symbols.TmfSymbolProviderUpdatedSignal;
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
* by CallGraphAnalysisUI.
*
* @author Sonia Farrah
*/
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;
private TimeGraphPresentationProvider fPresentationProvider;
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
*/
public FlameGraphView() {
super(ID);
}
@Override
public void createPartControl(Composite parent) {
super.createPartControl(parent);
fTimeGraphViewer = new TimeGraphViewer(parent, SWT.NONE);
fTimeGraphContentProvider = new FlameGraphContentProvider();
fPresentationProvider = new FlameGraphPresentationProvider();
fTimeGraphViewer.setTimeGraphContentProvider(fTimeGraphContentProvider);
fTimeGraphViewer.setTimeGraphProvider(fPresentationProvider);
IEditorPart editor = getSite().getPage().getActiveEditor();
if (editor instanceof ITmfTraceEditor) {
ITmfTrace trace = ((ITmfTraceEditor) editor).getTrace();
if (trace != null) {
traceSelected(new TmfTraceSelectedSignal(this, trace));
}
}
contributeToActionBars();
loadSortOption();
TmfSignalManager.register(this);
getSite().setSelectionProvider(fTimeGraphViewer.getSelectionProvider());
createTimeEventContextMenu();
fTimeGraphViewer.getTimeGraphControl().addMouseListener(new MouseAdapter() {
@Override
public void mouseDoubleClick(MouseEvent e) {
TimeGraphControl timeGraphControl = getTimeGraphViewer().getTimeGraphControl();
ISelection selection = timeGraphControl.getSelection();
if (selection instanceof IStructuredSelection) {
for (Object object : ((IStructuredSelection) selection).toList()) {
if (object instanceof FlamegraphEvent) {
FlamegraphEvent event = (FlamegraphEvent) object;
long startTime = event.getTime();
long endTime = startTime + event.getDuration();
getTimeGraphViewer().setStartFinishTime(startTime, endTime);
break;
}
}
}
}
});
}
/**
* Get the time graph viewer
*
* @return the time graph viewer
*/
@VisibleForTesting
public TimeGraphViewer getTimeGraphViewer() {
return fTimeGraphViewer;
}
/**
* Handler for the trace selected signal
*
* @param signal
* The incoming signal
*/
@TmfSignalHandler
public void traceSelected(final TmfTraceSelectedSignal signal) {
fTrace = signal.getTrace();
if (fTrace != null) {
CallGraphAnalysis flamegraphModule = TmfTraceUtils.getAnalysisModuleOfClass(fTrace, CallGraphAnalysis.class, CallGraphAnalysisUI.ID);
buildFlameGraph(flamegraphModule);
}
}
/**
* Get the necessary data for the flame graph and display it
*
* @param callGraphAnalysis
* the callGraphAnalysis
*/
@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;
}
};
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
*
* @param signal
* the signal received
*/
@TmfSignalHandler
public void traceClosed(final TmfTraceClosedSignal signal) {
if (signal.getTrace() == fTrace) {
fTimeGraphViewer.setInput(null);
}
}
@Override
public void setFocus() {
fTimeGraphViewer.setFocus();
}
// ------------------------------------------------------------------------
// Helper methods
// ------------------------------------------------------------------------
private void createTimeEventContextMenu() {
fEventMenuManager.setRemoveAllWhenShown(true);
TimeGraphControl timeGraphControl = fTimeGraphViewer.getTimeGraphControl();
final Menu timeEventMenu = fEventMenuManager.createContextMenu(timeGraphControl);
timeGraphControl.addTimeGraphEntryMenuListener(new MenuDetectListener() {
@Override
public void menuDetected(MenuDetectEvent event) {
/*
* The TimeGraphControl will call the TimeGraphEntryMenuListener
* before the TimeEventMenuListener. We need to clear the menu
* for the case the selection was done on the namespace where
* the time event listener below won't be called afterwards.
*/
timeGraphControl.setMenu(null);
event.doit = false;
}
});
timeGraphControl.addTimeEventMenuListener(new MenuDetectListener() {
@Override
public void menuDetected(MenuDetectEvent event) {
Menu menu = timeEventMenu;
if (event.data instanceof 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().getDurationStatistics().getMaxObject();
if (maxSeg == null) {
return;
}
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().getDurationStatistics().getMinObject();
if (minSeg == null) {
return;
}
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));
}
/**
* Symbol map provider updated
*
* @param signal
* the signal
*/
@TmfSignalHandler
public void symbolMapUpdated(TmfSymbolProviderUpdatedSignal signal) {
if (signal.getSource() != this) {
fTimeGraphViewer.refresh();
}
}
}