blob: d9c2a002cb128fa561a9cf418a45ed02dc1d4b1a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2018 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Elliott Baron <ebaron@redhat.com> - initial API and implementation
*******************************************************************************/
package org.eclipse.linuxtools.internal.valgrind.massif;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.ResourceLocator;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.IFontProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.linuxtools.dataviewers.charts.actions.SaveChartAction;
import org.eclipse.linuxtools.internal.valgrind.massif.MassifSnapshot.SnapshotType;
import org.eclipse.linuxtools.internal.valgrind.massif.charting.ChartEditorInput;
import org.eclipse.linuxtools.internal.valgrind.massif.charting.HeapChart;
import org.eclipse.linuxtools.valgrind.ui.IValgrindToolView;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
public class MassifViewPart extends ViewPart implements IValgrindToolView {
private static final String TITLE_STACKS = Messages
.getString("MassifViewPart.Stacks"); //$NON-NLS-1$
private static final String TITLE_EXTRA = Messages
.getString("MassifViewPart.Extra_Heap"); //$NON-NLS-1$
private static final String TITLE_USEFUL = Messages
.getString("MassifViewPart.Useful_Heap"); //$NON-NLS-1$
private static final String TITLE_TOTAL = Messages
.getString("MassifViewPart.Total"); //$NON-NLS-1$
private static final String TITLE_TIME = Messages
.getString("MassifViewPart.Time"); //$NON-NLS-1$
private static final String TITLE_NUMBER = Messages
.getString("MassifViewPart.Snapshot"); //$NON-NLS-1$
private static final String TREE_ACTION = MassifPlugin.PLUGIN_ID
+ ".treeAction"; //$NON-NLS-1$
public static final String CHART_ACTION = MassifPlugin.PLUGIN_ID
+ ".chartAction"; //$NON-NLS-1$
public static final String PID_ACTION = MassifPlugin.PLUGIN_ID
+ ".pidAction"; //$NON-NLS-1$
public static final String SAVE_CHART_ACTION = MassifPlugin.PLUGIN_ID
+ ".saveChartAction"; //$NON-NLS-1$
private MassifOutput output;
private Integer pid;
private Composite top;
private StackLayout stackLayout;
private TableViewer viewer;
private MassifTreeViewer treeViewer;
private MassifHeapTreeNode[] nodes;
private String chartName;
private static final int COLUMN_SIZE = 125;
private Action treeAction;
private MassifPidMenuAction pidAction;
private SaveChartAction saveChartAction;
private List<ChartEditorInput> chartInputs;
@Override
public void createPartControl(Composite parent) {
chartInputs = new ArrayList<>();
top = new Composite(parent, SWT.NONE);
stackLayout = new StackLayout();
top.setLayout(stackLayout);
top.setLayoutData(new GridData(GridData.FILL_BOTH));
viewer = new TableViewer(top, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL
| SWT.FULL_SELECTION);
Table table = viewer.getTable();
table.setLayoutData(new GridData(GridData.FILL_BOTH));
String[] columnTitles = { TITLE_NUMBER, TITLE_TIME, TITLE_TOTAL,
TITLE_USEFUL, TITLE_EXTRA, TITLE_STACKS };
for (String columnTitle : columnTitles) {
TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
column.getColumn().setText(columnTitle);
column.getColumn().setWidth(COLUMN_SIZE);
column.getColumn().setResizable(true);
column.getColumn().addSelectionListener(getHeaderListener());
}
table.setHeaderVisible(true);
table.setLinesVisible(true);
viewer.setContentProvider(new ArrayContentProvider());
viewer.setLabelProvider(new MassifLabelProvider());
treeViewer = new MassifTreeViewer(top);
treeViewer.getViewer().getControl().setLayoutData(new GridData(GridData.FILL_BOTH));
viewer.addDoubleClickListener(event -> {
MassifSnapshot snapshot = (MassifSnapshot) ((IStructuredSelection) event
.getSelection()).getFirstElement();
if (snapshot.getType() != SnapshotType.EMPTY) {
treeAction.setChecked(true);
setTopControl(treeViewer.getViewer().getControl());
treeViewer.getViewer().setSelection(new StructuredSelection(snapshot
.getRoot()), true);
treeViewer.getViewer().expandToLevel(snapshot.getRoot(),
AbstractTreeViewer.ALL_LEVELS);
}
});
stackLayout.topControl = viewer.getControl();
top.layout();
}
private String getUnitString(MassifSnapshot[] snapshots) {
String result;
MassifSnapshot snapshot = snapshots[0];
switch (snapshot.getUnit()) {
case BYTES:
result = "B"; //$NON-NLS-1$
break;
case INSTRUCTIONS:
result = "i"; //$NON-NLS-1$
break;
default:
result = "ms"; //$NON-NLS-1$
break;
}
return result;
}
private SelectionListener getHeaderListener() {
return SelectionListener.widgetSelectedAdapter(e -> {
TableColumn column = (TableColumn) e.widget;
Table table = viewer.getTable();
if (column.equals(table.getSortColumn())) {
int direction = table.getSortDirection() == SWT.UP ? SWT.DOWN : SWT.UP;
table.setSortDirection(direction);
} else {
table.setSortDirection(SWT.UP);
}
table.setSortColumn(column);
viewer.setComparator(new ViewerComparator() {
@Override
public int compare(Viewer viewer, Object o1, Object o2) {
Table table = ((TableViewer) viewer).getTable();
int direction = table.getSortDirection();
MassifSnapshot s1 = (MassifSnapshot) o1;
MassifSnapshot s2 = (MassifSnapshot) o2;
long result;
TableColumn column = table.getSortColumn();
if (column.getText().equals(TITLE_NUMBER)) {
result = s1.getNumber() - s2.getNumber();
} else if (column.getText().startsWith(TITLE_TIME)) {
result = s1.getTime() - s2.getTime();
} else if (column.getText().equals(TITLE_TOTAL)) {
result = s1.getTotal() - s2.getTotal();
} else if (column.getText().equals(TITLE_USEFUL)) {
result = s1.getHeapBytes() - s2.getHeapBytes();
} else if (column.getText().equals(TITLE_EXTRA)) {
result = s1.getHeapExtra() - s2.getHeapExtra();
} else {
result = s1.getStacks() - s2.getStacks();
}
// ascending / descending
result = direction == SWT.UP ? result : -result;
// overflow check
if (result > Integer.MAX_VALUE) {
result = Integer.MAX_VALUE;
} else if (result < Integer.MIN_VALUE) {
result = Integer.MIN_VALUE;
}
return (int) result;
}
});
});
}
@Override
public IAction[] getToolbarActions() {
pidAction = new MassifPidMenuAction(this);
pidAction.setId(PID_ACTION);
Action chartAction = new Action(
Messages.getString("MassifViewPart.Display_Heap_Allocation"), IAction.AS_PUSH_BUTTON) { //$NON-NLS-1$
@Override
public void run() {
ChartEditorInput input = getChartInput(pid);
if (input != null) {
displayChart(input);
}
}
};
chartAction.setId(CHART_ACTION);
chartAction.setImageDescriptor(
ResourceLocator.imageDescriptorFromBundle(MassifPlugin.PLUGIN_ID, "icons/linecharticon.gif").get()); //$NON-NLS-1$
chartAction.setToolTipText(Messages
.getString("MassifViewPart.Display_Heap_Allocation")); //$NON-NLS-1$
saveChartAction = new SaveChartAction();
saveChartAction.setId(SAVE_CHART_ACTION);
treeAction = new Action(
Messages.getString("MassifViewPart.Show_Heap_Tree"), IAction.AS_CHECK_BOX) { //$NON-NLS-1$
@Override
public void run() {
if (isChecked()) {
stackLayout.topControl = treeViewer.getViewer().getControl();
top.layout();
} else {
stackLayout.topControl = viewer.getControl();
top.layout();
}
}
};
treeAction.setId(TREE_ACTION);
treeAction.setImageDescriptor(
ResourceLocator.imageDescriptorFromBundle(MassifPlugin.PLUGIN_ID, "icons/call_hierarchy.gif").get()); //$NON-NLS-1$
treeAction.setToolTipText(Messages
.getString("MassifViewPart.Show_Heap_Tree")); //$NON-NLS-1$
return new IAction[] { pidAction, chartAction, saveChartAction, treeAction };
}
private void createChart(MassifSnapshot[] snapshots) {
String title = chartName + " [PID: " + pid + "]"; //$NON-NLS-1$//$NON-NLS-2$
HeapChart chart = new HeapChart(snapshots, title);
String name = getInputName(title);
ChartEditorInput input = new ChartEditorInput(chart, this, name, pid);
chartInputs.add(input);
// open the editor
displayChart(input);
}
private void displayChart(final ChartEditorInput chartInput) {
Display.getDefault().syncExec(() -> {
try {
IWorkbenchPage page = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage();
page.openEditor(chartInput, MassifPlugin.EDITOR_ID);
} catch (PartInitException e) {
e.printStackTrace();
}
});
saveChartAction.setChart(chartInput.getChart().getChartControl(), chartInput.getName());
}
private String getInputName(String description) {
String launchName;
try {
launchName = description.substring(0, description
.indexOf("[massif")); //$NON-NLS-1$
}
catch(IndexOutOfBoundsException e) {
launchName = "(No chart title)"; //$NON-NLS-1$
}
return launchName.trim();
}
@Override
public void setFocus() {
viewer.getTable().setFocus();
}
@Override
public void refreshView() {
if (output != null && pid != null) {
saveChartAction.setChart(null);
MassifSnapshot[] snapshots = output.getSnapshots(pid);
pidAction.setPids(output.getPids());
if (snapshots != null) {
viewer.setInput(snapshots);
String timeWithUnit = TITLE_TIME + " (" + getUnitString(snapshots) + ")"; //$NON-NLS-1$ //$NON-NLS-2$
for (TableColumn column : viewer.getTable().getColumns()) {
if (column.getText().startsWith(TITLE_TIME)) {
column.setText(timeWithUnit);
}
viewer.getTable().layout(true);
}
MassifSnapshot[] detailed = getDetailed(snapshots);
nodes = new MassifHeapTreeNode[detailed.length];
for (int i = 0; i < detailed.length; i++) {
nodes[i] = detailed[i].getRoot();
}
treeViewer.getViewer().setInput(nodes);
// create and display chart
if (snapshots.length > 0) {
ChartEditorInput input = getChartInput(pid);
if (input == null) {
createChart(snapshots);
}
else {
displayChart(input);
}
}
}
}
}
@Override
public void dispose() {
// Close all chart editors to keep Valgrind output consistent throughout workbench
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
if (page != null) {
for (IEditorInput input : chartInputs) {
IEditorPart part = page.findEditor(input);
if (part != null) {
page.closeEditor(part, false);
}
}
}
super.dispose();
}
public void setTopControl(Control control) {
stackLayout.topControl = control;
top.layout(true);
}
public void setOutput(MassifOutput output) {
this.output = output;
}
public MassifOutput getOutput() {
return output;
}
public void setPid(Integer pid) {
this.pid = pid;
}
public Integer getPid() {
return pid;
}
public MassifSnapshot[] getSnapshots() {
return output != null && pid != null ? output.getSnapshots(pid) : null;
}
public TableViewer getTableViewer() {
return viewer;
}
public MassifTreeViewer getTreeViewer() {
return treeViewer;
}
private static class MassifLabelProvider extends LabelProvider implements
ITableLabelProvider, IFontProvider {
@Override
public Image getColumnImage(Object element, int columnIndex) {
Image image = null;
if (columnIndex == 0) {
MassifSnapshot snapshot = (MassifSnapshot) element;
switch (snapshot.getType()) {
case EMPTY:
break;
case PEAK:
case DETAILED:
image = ResourceLocator
.imageDescriptorFromBundle(MassifPlugin.PLUGIN_ID, "icons/call_hierarchy.gif").get() //$NON-NLS-1$
.createImage();
}
}
return image;
}
@Override
public String getColumnText(Object element, int columnIndex) {
MassifSnapshot snapshot = (MassifSnapshot) element;
DecimalFormat df = new DecimalFormat("#,##0"); //$NON-NLS-1$
switch (columnIndex) {
case 0:
return df.format(snapshot.getNumber());
case 1:
return df.format(snapshot.getTime());
case 2:
return df.format(snapshot.getTotal());
case 3:
return df.format(snapshot.getHeapBytes());
case 4:
return df.format(snapshot.getHeapExtra());
default:
return df.format(snapshot.getStacks());
}
}
@Override
public Font getFont(Object element) {
Font font = null;
MassifSnapshot snapshot = (MassifSnapshot) element;
switch (snapshot.getType()) {
case EMPTY:
case DETAILED:
break;
case PEAK:
font = JFaceResources.getFontRegistry().getBold(
JFaceResources.DIALOG_FONT);
}
return font;
}
}
public void setChartName(String chartName) {
this.chartName = chartName;
}
private MassifSnapshot[] getDetailed(MassifSnapshot[] snapshots) {
ArrayList<MassifSnapshot> list = new ArrayList<>();
for (MassifSnapshot snapshot : snapshots) {
if (snapshot.getType() != SnapshotType.EMPTY) {
list.add(snapshot);
}
}
return list.toArray(new MassifSnapshot[list.size()]);
}
private ChartEditorInput getChartInput(Integer pid) {
ChartEditorInput result = null;
for (ChartEditorInput input : chartInputs) {
if (input.getPid().equals(pid)) {
result = input;
}
}
return result;
}
}