| /******************************************************************************* |
| * 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; |
| } |
| } |