blob: 39d8df1eaaef5fe1179e416e7b55ce7bf619d591 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2021 SAP AG and IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* SAP AG - initial API and implementation
* IBM Corporation - refactor for new/import wizard
* IBM Corporation - disabled dumps
*******************************************************************************/
package org.eclipse.mat.ui.internal.acquire;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.internal.acquire.HeapDumpProviderDescriptor;
import org.eclipse.mat.internal.acquire.HeapDumpProviderRegistry;
import org.eclipse.mat.internal.acquire.VmInfoDescriptor;
import org.eclipse.mat.query.registry.AnnotatedObjectArgumentsSet;
import org.eclipse.mat.snapshot.acquire.VmInfo;
import org.eclipse.mat.ui.MemoryAnalyserPlugin;
import org.eclipse.mat.ui.Messages;
import org.eclipse.mat.ui.internal.acquire.AcquireSnapshotAction.AcquireWizard;
import org.eclipse.mat.ui.util.ErrorHelper;
import org.eclipse.mat.ui.util.ProgressMonitorWrapper;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
/**
* Handles the list of all VMs.
*
*/
public class AcquireDialog extends WizardPage
{
private static final String LAST_DIRECTORY_KEY = AcquireDialog.class.getName() + ".lastDir"; //$NON-NLS-1$
private LocalResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources());
private Font italicFont;
private Table localVMsTable;
private Label saveLocationLabel;
private Text folderText;
private Button configureButton;
private Button refreshButton;
private Collection<HeapDumpProviderDescriptor> providerDescriptors;
private List<ProcessSelectionListener> listeners = new ArrayList<ProcessSelectionListener>();
private int sortpid = 1;
private int sortproc = 2;
private List<? extends VmInfo> vms;
interface ProcessSelectionListener
{
void processSelected(AnnotatedObjectArgumentsSet argumentSet);
}
public AcquireDialog(Collection<HeapDumpProviderDescriptor> dumpProviders)
{
super("acq"); //$NON-NLS-1$
this.providerDescriptors = dumpProviders;
}
public void createControl(Composite parent)
{
setTitle(Messages.AcquireDialog_DialogName);
setDescription(Messages.AcquireDialog_DialogDescription);
final Composite top = new Composite(parent, SWT.NONE);
top.setLayout(new GridLayout(2, false));
Label l1 = new Label(top, SWT.NONE);
l1.setText(Messages.AcquireDialog_ChooseProcess);
GridDataFactory.swtDefaults().span(2, 1).applyTo(l1);
Composite tableComposite = new Composite(top, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, true).indent(0, 0).applyTo(tableComposite);
TableColumnLayout tableColumnLayout = new TableColumnLayout();
tableComposite.setLayout(tableColumnLayout);
localVMsTable = new Table(tableComposite, SWT.BORDER | SWT.SINGLE | SWT.V_SCROLL | SWT.FULL_SELECTION);
localVMsTable.setHeaderVisible(true);
localVMsTable.setLinesVisible(true);
GridDataFactory.fillDefaults().grab(true, true).span(1, 1).minSize(0, 100).applyTo(localVMsTable);
localVMsTable.addSelectionListener(new SelectionAdapter()
{
public void widgetSelected(SelectionEvent e)
{
selectionChanged();
}
public void widgetDefaultSelected(SelectionEvent e)
{
if (getWizard().performFinish())
getShell().close();
}
});
final int descWidth = 250;
final int pidWidth = 70;
final int provWidth = 200;
TableColumn column = new TableColumn(localVMsTable, SWT.RIGHT);
column.setText(Messages.AcquireDialog_ColumnDescription);
tableColumnLayout.setColumnData(column, new ColumnWeightData(100, descWidth));
column = new TableColumn(localVMsTable, SWT.RIGHT);
column.setText(Messages.AcquireDialog_ColumnPID);
tableColumnLayout.setColumnData(column, new ColumnWeightData(0, pidWidth));
column.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent arg0)
{
if (sortpid == 0)
{
sortpid = 2;
}
else if (sortpid > 0)
{
sortpid = -2;
}
else
{
sortpid = 0;
}
if (Math.abs(sortproc) >= 2)
sortproc /= 2;
resort();
}
public void widgetDefaultSelected(SelectionEvent arg0)
{
}
});
column = new TableColumn(localVMsTable, SWT.LEFT);
column.setText(Messages.AcquireDialog_HeapDumpProviderColumnHeader);
tableColumnLayout.setColumnData(column, new ColumnWeightData(0, provWidth));
column.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent arg0)
{
if (sortproc == 0)
{
sortproc = 2;
}
else if (sortproc > 0)
{
sortproc = -2;
}
else
{
sortproc = 0;
}
if (Math.abs(sortpid) >= 2)
sortpid /= 2;
resort();
}
public void widgetDefaultSelected(SelectionEvent arg0)
{
}
});
tableComposite.layout();
tableComposite.pack();
Control control = localVMsTable.getParent();
PlatformUI.getWorkbench().getHelpSystem().setHelp(control, "org.eclipse.mat.ui.help.acquire_arguments"); //$NON-NLS-1$
italicFont = resourceManager.createFont(FontDescriptor.createFrom(column.getParent().getFont()).setStyle(SWT.ITALIC));
Composite buttons = new Composite(top, SWT.NONE);
buttons.setLayout(new GridLayout(1, false));
GridDataFactory.fillDefaults().span(1, 1).applyTo(buttons);
refreshButton = new Button(buttons, SWT.NONE);
refreshButton.setText(Messages.AcquireDialog_RefreshButtonLabel);
GridDataFactory.fillDefaults().grab(true, false).span(1, 1).applyTo(refreshButton);
refreshButton.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e)
{
refresh();
}
});
configureButton = new Button(buttons, SWT.NONE);
configureButton.setText(Messages.AcquireDialog_ConfigureButtonLabel);
GridDataFactory.fillDefaults().grab(true, false).span(1, 1).applyTo(configureButton);
configureButton.addSelectionListener(new SelectionAdapter()
{
public void widgetSelected(SelectionEvent e)
{
AcquireWizard aw = (AcquireWizard)AcquireDialog.this.getWizard();
aw.getContainer().showPage(aw.configPage);
}
});
saveLocationLabel = new Label(top, SWT.NONE);
saveLocationLabel.setText(Messages.AcquireDialog_SaveLocation);
GridDataFactory.swtDefaults().span(2, 1).applyTo(saveLocationLabel);
folderText = new Text(top, SWT.BORDER);
GridDataFactory.fillDefaults().minSize(300, 0).grab(true, false).applyTo(folderText);
String lastDir = Platform.getPreferencesService().getString(MemoryAnalyserPlugin.PLUGIN_ID, LAST_DIRECTORY_KEY, "", null); //$NON-NLS-1$
if (lastDir == null || lastDir.trim().equals("")) //$NON-NLS-1$
lastDir = System.getProperty("user.home"); //$NON-NLS-1$
folderText.setText(lastDir);
folderText.addModifyListener(new ModifyListener()
{
public void modifyText(ModifyEvent e)
{
getContainer().updateButtons();
String errorMessage = null;
int level= NONE;
if (localVMsTable.getSelectionIndex() != -1 && folderText.getText().length() > 0)
{
File f = new File(AcquireDialog.this.getSelectedPath());
if (f.isDirectory())
{
errorMessage = Messages.AcquireDialog_FileIsDirectory;
level = ERROR;
}
else if (f.exists())
{
errorMessage = Messages.AcquireDialog_FileExists;
level = WARNING;
}
}
AcquireDialog.this.setMessage(errorMessage, level);
}
});
Button b = new Button(top, SWT.NONE);
b.setText(Messages.AcquireDialog_BrowseButton);
b.addSelectionListener(new SelectionAdapter()
{
public void widgetSelected(SelectionEvent e)
{
String folder;
if (localVMsTable.getSelectionIndex() == -1)
{
// No VM selected, so just choose the directory as the file will be
// overwritten once a VM is chosen.
DirectoryDialog dialog = new DirectoryDialog(top.getShell());
dialog.setFilterPath(AcquireDialog.this.getSelectedDirectory());
dialog.setText(Messages.AcquireDialog_ChooseDestinationDirectory);
dialog.setMessage(Messages.AcquireDialog_ChooseDestinationDirectoryMessage);
folder = dialog.open();
}
else
{
FileDialog dialog = new FileDialog(top.getShell(), SWT.SAVE);
File f = new File(AcquireDialog.this.getSelectedPath());
if (f.isDirectory())
{
dialog.setFilterPath(f.getPath());
}
else
{
dialog.setFilterPath(f.getParent());
dialog.setFileName(f.getName());
String name = f.getName();
int i = name.lastIndexOf('.');
if (i >= 0)
{
dialog.getFilterExtensions();
dialog.setFilterExtensions(new String[] { "*" + name.substring(i), "*" }); //$NON-NLS-1$ //$NON-NLS-2$
}
}
dialog.setText(Messages.AcquireDialog_ChooseDestinationDirectoryAndFile);
dialog.setOverwrite(true);
folder = dialog.open();
}
if (folder != null && folder.length() > 0)
folderText.setText(folder);
}
});
localVMsTable.setFocus();
setControl(top);
// Delay retrieving the VM information until the wizard is displayed
getControl().getDisplay().asyncExec(new Runnable()
{
public void run()
{
refresh();
}
});
}
private void refreshTable()
{
localVMsTable.removeAll();
vms = getAvailableVms();
fillTable();
}
private void fillTable()
{
localVMsTable.removeAll();
if (vms != null)
{
for (VmInfo process : sort(vms))
{
try
{
VmInfoDescriptor descriptor = VmInfoDescriptor.createDescriptor(process);
TableItem item = new TableItem(localVMsTable, SWT.NONE);
item.setGrayed(!process.isHeapDumpEnabled());
if (!process.isHeapDumpEnabled())
{
item.setFont(italicFont);
}
item.setText(0, process.getDescription());
item.setText(1, Integer.toString(process.getPid()));
item.setText(2, getProviderDescriptor(process).getName());
item.setData(new AnnotatedObjectArgumentsSet(descriptor));
}
catch (SnapshotException e)
{
Logger.getLogger(MemoryAnalyserPlugin.PLUGIN_ID).log(Level.SEVERE, "Problems refreshing process list", e); //$NON-NLS-1$
}
}
}
}
private List<? extends VmInfo> sort(List<? extends VmInfo> vms)
{
if (sortpid == 0 && sortproc == 0)
return vms;
List<? extends VmInfo>vms2 = new ArrayList<VmInfo>(vms);
Collections.sort(vms2, new Comparator<VmInfo>() {
private int sort(int a, int b) {
if (a < b)
return -1;
else if (a > b)
return +1;
else
return 0;
}
private int signum(int x)
{
return sort(x, 0);
}
public int compare(VmInfo o1, VmInfo o2)
{
// First sort by indicator +/- 2, then by the indicator +/- 1
return sortpid * sort(o1.getPid(), o2.getPid()) + sortproc *
signum(AcquireDialog.this.getProviderDescriptor(o1).getName().compareTo(
AcquireDialog.this.getProviderDescriptor(o2).getName()));
}
});
return vms2;
}
private List<VmInfo> getAvailableVms()
{
List<VmInfo> vms = new ArrayList<VmInfo>();
for (HeapDumpProviderDescriptor providerDescriptor : providerDescriptors)
{
GetVMListRunnable getListOperation = new GetVMListRunnable(providerDescriptor, getContainer());
if (getListOperation.run().isOK())
{
List<? extends VmInfo> providerVMs = getListOperation.getResult();
if (providerVMs != null)
{
vms.addAll(providerVMs);
}
}
}
return vms;
}
@Override
public boolean isPageComplete()
{
// There needs to be a valid process and a valid dump name
// Also if the heap dump is disabled (indicated by italic font), also disable (getGrayed() doesn't work).
if (localVMsTable.getSelectionIndex() == -1)
{
return false;
}
// See if the enabled status has changed (for example getting a dump has failed).
boolean isEnabled = !localVMsTable.getItem(localVMsTable.getSelectionIndex()).getFont().equals(italicFont);
boolean isEnabled2 = ((VmInfoDescriptor)((AnnotatedObjectArgumentsSet)localVMsTable.getItem(localVMsTable.getSelectionIndex()).getData()).getDescriptor()).getVmInfo().isHeapDumpEnabled();
if (isEnabled && !isEnabled2)
{
// newly disabled
localVMsTable.getItem(localVMsTable.getSelectionIndex()).setFont(italicFont);
isEnabled = false;
}
return localVMsTable.getSelectionIndex() != -1 && isEnabled &&
folderText.getText().length() > 0 && !(new File(getSelectedPath()).isDirectory());
}
public AnnotatedObjectArgumentsSet getProcessArgumentsSet()
{
if (localVMsTable.getSelectionIndex() == -1) return null;
AnnotatedObjectArgumentsSet argumentsSet = (AnnotatedObjectArgumentsSet) localVMsTable.getSelection()[0].getData();
return argumentsSet;
}
public VmInfo getProcess()
{
if (localVMsTable.getSelectionIndex() == -1) return null;
AnnotatedObjectArgumentsSet argumentsSet = (AnnotatedObjectArgumentsSet) localVMsTable.getSelection()[0].getData();
VmInfoDescriptor descriptor = (VmInfoDescriptor) argumentsSet.getDescriptor();
return descriptor.getVmInfo();
}
public String getSelectedPath()
{
return folderText.getText();
}
private String getSelectedDirectory()
{
String selectedPath = folderText.getText();
if (selectedPath == null)
return ""; //$NON-NLS-1$
// if the selection is a folder, just return it
File f = new File(selectedPath);
if (f.isDirectory())
return selectedPath;
// otherwise return what seems to be the deepest folder
String dir = f.getParent();
return dir == null ? selectedPath : dir;
}
public void saveSettings()
{
InstanceScope.INSTANCE.getNode(MemoryAnalyserPlugin.PLUGIN_ID).put(LAST_DIRECTORY_KEY, getSelectedDirectory());
}
private HeapDumpProviderDescriptor getProviderDescriptor(VmInfo vmInfo)
{
return HeapDumpProviderRegistry.instance().getHeapDumpProvider(vmInfo.getHeapDumpProvider().getClass());
}
synchronized void addProcessSelectionListener(ProcessSelectionListener listener)
{
listeners.add(listener);
}
void clearSelection() {
localVMsTable.deselectAll();
selectionChanged();
}
void refresh() {
localVMsTable.deselectAll();
selectionChanged();
refreshTable();
}
void resort() {
localVMsTable.deselectAll();
selectionChanged();
fillTable();
}
void updateFileName()
{
VmInfo process = getProcess();
if (process == null)
{
folderText.setText(getSelectedDirectory());
saveLocationLabel.setText(Messages.AcquireDialog_SaveLocation);
saveLocationLabel.pack();
} else {
String proposedFileName = process.getProposedFileName();
if (proposedFileName == null)
proposedFileName = "java_%pid%"; //$NON-NLS-1$
proposedFileName = proposedFileName.replace("%pid%", String.valueOf(getProcess().getPid())); //$NON-NLS-1$
// Also replace date, time, and sequence number in {0} {1} {2}
int pid = process.getPid();
Date date = new Date();
String errorMessage = null;
String proposedFilePath;
int i = 1;
do
{
String proposedFileName2;
try
{
proposedFileName2 = MessageUtil.format(proposedFileName, date, pid, i);
}
catch (IllegalArgumentException e)
{
errorMessage = e.getLocalizedMessage();
proposedFileName2 = proposedFileName;
}
final File proposedFile = new File(getSelectedDirectory(), proposedFileName2);
proposedFilePath = proposedFile.getPath();
if (proposedFileName2.equals(proposedFileName) || !proposedFile.exists())
break;
}
while (++i < 10000);
folderText.setText(proposedFilePath);
saveLocationLabel.setText(Messages.AcquireDialog_SaveFileLocation);
saveLocationLabel.pack();
if (errorMessage != null)
{
// Set error after setting the text, which would clear the error message
setMessage(MessageUtil.format(Messages.AcquireDialog_InvalidFilenameTemplate, errorMessage, getProviderDescriptor(process).getName()), WARNING);
}
}
}
private void selectionChanged()
{
updateFileName();
// notify listeners
for (ProcessSelectionListener listener : listeners)
{
listener.processSelected(getProcessArgumentsSet());
}
// The button states might depend on the listeners changing
getContainer().updateButtons();
}
private static class GetVMListRunnable implements IRunnableWithProgress
{
private IStatus status;
private IRunnableContext context;
private List<? extends VmInfo> result;
private HeapDumpProviderDescriptor provider;
public GetVMListRunnable(HeapDumpProviderDescriptor provider, IRunnableContext context)
{
this.provider = provider;
this.context = context;
}
public List<? extends VmInfo> getResult()
{
return result;
}
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException
{
status = doOperation(monitor);
}
private IStatus doOperation(IProgressMonitor monitor)
{
IProgressListener listener = new ProgressMonitorWrapper(monitor);
try
{
result = provider.getHeapDumpProvider().getAvailableVMs(listener);
if (listener.isCanceled()) return Status.CANCEL_STATUS;
}
catch (Exception e)
{
return ErrorHelper.createErrorStatus(e);
}
return Status.OK_STATUS;
}
public final IStatus run()
{
try
{
context.run(true, true, this);
}
catch (Exception e)
{
status = ErrorHelper.createErrorStatus(Messages.AcquireSnapshotAction_UnexpectedException, e);
}
// report error if any occurred
if (!status.isOK() && status != Status.CANCEL_STATUS)
Logger.getLogger(MemoryAnalyserPlugin.PLUGIN_ID).log(Level.INFO, MessageUtil.format("Error getting list of VMs with [{0}] provider", provider.getName()), status.getException()); //$NON-NLS-1$
return status;
}
}
}