blob: 0ee902673fa61851a96da895b2c03a78b71648e1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2019 IBM Corporation
* 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
*
* Contributors:
* IBM Corporation - initial implementation
* IBM Corporation/Andrew Johnson - updates for calling com.ibm/com.sun classes via reflection
* IBM Corporation/Andrew Johnson - hprof and allow option for GC before dump
*******************************************************************************/
package org.eclipse.mat.ibmvm.acquire;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.query.annotations.Argument;
import org.eclipse.mat.query.annotations.HelpUrl;
import org.eclipse.mat.query.annotations.Name;
import org.eclipse.mat.snapshot.acquire.IHeapDumpProvider;
import org.eclipse.mat.snapshot.acquire.VmInfo;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.IProgressListener.Severity;
import org.eclipse.mat.util.MessageUtil;
import org.osgi.service.prefs.BackingStoreException;
/**
* Enables the creation of dumps from IBM VMs when a non-IBM VM
* or old IBM VM is used to run Memory Analyzer. A new IBM VM is
* used as a helper VM.
* @author ajohnson
*
*/
@Name("IBM Dump (using helper VM)")
@HelpUrl("/org.eclipse.mat.ui.help/tasks/acquiringheapdump.html#task_acquiringheapdump__3")
public class IBMExecDumpProvider extends BaseProvider
{
private static final String PLUGIN_ID = "org.eclipse.mat.ibmdump"; //$NON-NLS-1$
private static final String JAVA_EXEC = "java"; //$NON-NLS-1$
private static boolean abort = false;
private int lastCount = 20;
@Argument(isMandatory = false)
public File javaexecutable;
@Argument(isMandatory = false)
public String vmoptions[] = {}; //$NON-NLS-1$
public IBMExecDumpProvider()
{
// See if an IBM VM or an Oracle VM
try
{
Class.forName("com.ibm.jvm.Dump");
}
catch (ClassNotFoundException e)
{
// Looks like no System dump is available
defaultType = DumpType.HPROF;
}
}
public File acquireDump(VmInfo info, File preferredLocation, IProgressListener listener) throws SnapshotException
{
listener.beginTask(Messages.getString("IBMExecDumpProvider.GeneratingDump"), TOTAL_WORK); //$NON-NLS-1$
String encoding = System.getProperty("file.encoding", "UTF-8"); //$NON-NLS-1$//$NON-NLS-2$
String encodingOpt = "-Dfile.encoding="+encoding; //$NON-NLS-1$
ProcessBuilder pb = new ProcessBuilder();
Process p = null;
final IBMExecVmInfo info2 = (IBMExecVmInfo) info;
String vm = info2.getPidName();
try
{
String jar = getExecJar().getAbsolutePath();
final String execPath = info2.javaexecutable.getPath();
List<String> args = new ArrayList<String>(9);
args.add(execPath);
args.add(encodingOpt);
if (info2.vmoptions != null)
{
args.addAll(Arrays.asList(info2.vmoptions));
}
args.add("-jar"); //$NON-NLS-1$
args.add(jar);
args.add(info2.type.toString());
args.add(vm);
args.add(Boolean.toString(info2.live));
args.add(Boolean.toString(info2.compress));
args.add(preferredLocation.getAbsolutePath());
if (info2.dumpdir != null)
args.add(info2.dumpdir.getAbsolutePath());
pb.command(args);
p = pb.start();
StringBuffer err = new StringBuffer();
StringBuffer in = new StringBuffer();
InputStreamReader os = new InputStreamReader(p.getInputStream(), encoding);
try
{
InputStreamReader es = new InputStreamReader(p.getErrorStream(), encoding);
try
{
int rc = 0;
do
{
while (os.ready())
{
in.append((char) os.read());
}
while (es.ready())
{
int c = es.read();
if (c == '.')
listener.worked(1);
err.append((char) c);
}
try
{
rc = p.exitValue();
break;
}
catch (IllegalThreadStateException e)
{
try
{
Thread.sleep(SLEEP_TIMEOUT);
}
catch (InterruptedException e1)
{
listener.setCanceled(true);
}
}
if (listener.isCanceled())
{
return null;
}
}
while (true);
if (rc != 0)
{
// Remove the dots as they don't add much to the exception
int dot = 0;
while (err.charAt(dot) == '.')
{
++dot;
}
err.delete(0, dot);
if (err.indexOf(IBMDumpProvider.AttachNotSupportedException.class.getName()) >= 0)
{
// Trying again won't work
info.setHeapDumpEnabled(false);
}
throw new IOException(MessageUtil.format(Messages
.getString("IBMExecDumpProvider.ReturnCode"), execPath, rc, err.toString())); //$NON-NLS-1$
}
String ss[] = in.toString().split("[\\n\\r]+"); //$NON-NLS-1$
String filename = ss[0];
listener.done();
final File file = new File(filename);
if (!file.canRead()) { throw new FileNotFoundException(filename); }
return file;
}
finally
{
es.close();
}
}
finally
{
try
{
p.exitValue();
}
catch (IllegalThreadStateException e)
{
p.destroy();
}
os.close();
}
}
catch (FileNotFoundException e)
{
throw new SnapshotException(e);
}
catch (IOException e)
{
throw new SnapshotException(e);
}
}
private File javaExec(String dir)
{
return jvmExec(dir, JAVA_EXEC);
}
static File jvmExec(String javaDir, String exec)
{
File javaExec;
if (javaDir != null)
{
javaExec = new File(javaDir, exec);
}
else
{
javaExec = new File(exec);
}
return javaExec;
}
public List<VmInfo> getAvailableVMs(IProgressListener listener)
{
List<VmInfo> ret;
if (abort)
return Collections.<VmInfo>emptyList();
/*
* 1.Try previous/no directory 2.Query directory - based on
* previous/Java.home
*/
File javaExec = javaexecutable;
String javaDir;
if (javaExec != null)
{
javaDir = javaExec.getParent();
}
else
{
javaDir = lastJavaDir();
if (javaDir == null)
javaDir = defaultJavaDir();
javaExec = javaExec(javaDir);
}
ret = execGetVMs(javaExec, listener);
if (ret != null)
{
setJavaDir(javaDir);
this.javaexecutable = javaExec;
}
return ret;
}
/**
* Guess a suitable VM to suggest to the user
*
* @return
*/
private String defaultJavaDir()
{
String ibmmodules[] = {"dgcollector.dll", "dgcollector.so", "jdmpview.exe", "jdmpview"}; //$NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ $NON-NLS-4$
String path = System.getenv("PATH"); //$NON-NLS-1$
if (path != null)
{
for (String p : path.split(File.pathSeparator))
{
File dir = new File(p);
File parentDir = dir.getParentFile();
// Recent IBM VMs have diagnostics collector and late attach
for (String mod : ibmmodules)
{
File dll = new File(dir, mod);
if (dll.canRead())
{
return dir.getPath();
}
}
// Perhaps we were given the sdk/bin directory, so look for the
// sdk/jre/bin
dir = new File(parentDir, "jre"); //$NON-NLS-1$
dir = new File(dir, "bin"); //$NON-NLS-1$
for (String mod : ibmmodules)
{
File dll = new File(dir, mod);
if (dll.canRead())
{
return dir.getPath();
}
}
}
}
String home = System.getProperty("java.home"); //$NON-NLS-1$
return new File(home, "bin").getPath(); //$NON-NLS-1$
}
private static final String last_directory_key = IBMExecDumpProvider.class.getName() + ".lastDir"; //$NON-NLS-1$
private static String savedJavaDir;
private static synchronized String lastJavaDir()
{
String home = Platform.getPreferencesService().getString(PLUGIN_ID, last_directory_key, savedJavaDir, null);
return home;
}
private static synchronized void setJavaDir(String home)
{
if (home != null && !home.equals(savedJavaDir))
{
IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(PLUGIN_ID);
prefs.put(last_directory_key, home);
try
{
prefs.flush();
}
catch (BackingStoreException e)
{
}
savedJavaDir = home;
}
}
/**
* Get list of VMs
* @param javaExec
* @param listener
* @return List of VMs - might be empty if there is an error
*/
private List<VmInfo> execGetVMs(File javaExec, IProgressListener listener)
{
ArrayList<VmInfo> ar = new ArrayList<VmInfo>();
listener.beginTask(Messages.getString("IBMExecDumpProvider.ListingIBMVMs"), lastCount);
int count = 0;
String encoding = System.getProperty("file.encoding", "UTF-8"); //$NON-NLS-1$//$NON-NLS-2$
String encodingOpt = "-Dfile.encoding="+encoding; //$NON-NLS-1$
ProcessBuilder pb = new ProcessBuilder();
Process p = null;
final String execPath = javaExec.getPath();
try
{
String jar = getExecJar().getAbsolutePath();
List<String> args = new ArrayList<String>(4);
args.add(execPath);
args.add(encodingOpt);
if (vmoptions != null)
{
args.addAll(Arrays.asList(vmoptions));
}
args.add("-jar"); //$NON-NLS-1$
args.add(jar);
// Verbose listing?
args.add(Boolean.toString(listAttach));
pb.command(args);
p = pb.start();
StringBuffer err = new StringBuffer();
StringBuffer in = new StringBuffer();
InputStreamReader os = new InputStreamReader(p.getInputStream(), encoding);
try
{
InputStreamReader es = new InputStreamReader(p.getErrorStream(), encoding);
try
{
int rc = 0;
do
{
while (os.ready())
{
in.append((char) os.read());
}
while (es.ready())
{
char read = (char) es.read();
err.append(read);
if (read == '.')
{
// IBMDumpProvider prints a dot for each thing worked
listener.worked(1);
++count;
}
}
try
{
rc = p.exitValue();
break;
}
catch (IllegalThreadStateException e)
{
Thread.sleep(SLEEP_TIMEOUT);
}
if (listener.isCanceled())
{
p.destroy();
// User cancelled, so perhaps attaching for details was a bad idea
listAttach = false;
break;
}
}
while (true);
if (rc != 0)
{
listener.sendUserMessage(Severity.WARNING,
MessageUtil.format(Messages.getString("IBMExecDumpProvider.ProblemListingVMsRC"), execPath, rc, err.toString()), null); //$NON-NLS-1$
return ar;
}
String ss[] = in.toString().split("[\\n\\r]+"); //$NON-NLS-1$
for (String s : ss)
{
// pid;dump enabled;dump type;proposed filename;possible directory;description
String s2[] = s.split(INFO_SEPARATOR, 6);
if (s2.length >= 5)
{
// Exclude the helper process
if (!s2[5].contains(getExecJar().getName()))
{
boolean enableDump = Boolean.parseBoolean(s2[1]);
IBMExecVmInfo ifo = new IBMExecVmInfo(s2[0], s2[5], enableDump, null, this);
ifo.javaexecutable = javaExec;
ifo.vmoptions = vmoptions;
/*
* Get the suggested dump type from the exec program.
* If it was an IBM VM, and this is one too, retain our suggested type,
* otherwise use the type suggested by the exec program.
*/
DumpType t = DumpType.valueOf(s2[2]);
if (isIBMDumpType(t) && isIBMDumpType(defaultType))
ifo.type = defaultType;
else
ifo.type = t;
ifo.live = defaultLive;
ifo.compress = defaultCompress;
if (s2[4].length() > 0)
ifo.dumpdir = new File(s2[4]);
if (s2[3].length() > 0 && !s2[3].equals(ifo.getProposedFileName()))
{
// Only set the name if the automatic naming is not applied
ifo.setProposedFileName(s2[3]);
}
ar.add(ifo);
}
}
}
}
finally
{
es.close();
}
}
finally
{
os.close();
}
}
catch (IOException e)
{
listener.sendUserMessage(Severity.WARNING, MessageUtil.format(Messages.getString("IBMExecDumpProvider.ProblemListingVMs"), execPath), e); //$NON-NLS-1$
}
catch (InterruptedException e)
{
listener.sendUserMessage(Severity.WARNING, MessageUtil.format(Messages.getString("IBMExecDumpProvider.ProblemListingVMs"), execPath), e); //$NON-NLS-1$
}
listener.done();
// Remember the count of progress as an estimate for next time
if (count > 0)
{
lastCount = count;
}
return ar;
}
private boolean isIBMDumpType(DumpType t)
{
return t == DumpType.SYSTEM || t == DumpType.HEAP || t == DumpType.JAVA;
}
private static File execJar;
static synchronized File getExecJar() throws IOException
{
if (execJar == null || !execJar.canRead())
{
String jarname = "org.eclipse.mat.ibmexecdumps"; //$NON-NLS-1$
// Must add all classes in IBMDumpProvider.java
String classesNames[] = {"org.eclipse.mat.ibmvm.acquire.IBMDumpProvider", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMDumpProvider$AgentInitializationException", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMDumpProvider$AgentLoadException", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMDumpProvider$AttachNotSupportedException", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMDumpProvider$AttachOperationFailedException", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMDumpProvider$AttachProvider", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMDumpProvider$VirtualMachine", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMDumpProvider$VirtualMachineDescriptor", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMDumpProvider$AgentLoader", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMDumpProvider$FileComparator", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMDumpProvider$NewFileFilter", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMDumpProvider$StderrProgressListener", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.BaseProvider", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.HprofDumpProvider", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMHeapDumpProvider", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMSystemDumpProvider", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMJavaDumpProvider", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.IBMVmInfo", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.AgentLoader2", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.acquire.DumpType", //$NON-NLS-1$
"org.eclipse.mat.ibmvm.agent.DumpAgent" }; //$NON-NLS-1$
Class<?> classes[] = { SnapshotException.class, IHeapDumpProvider.class, VmInfo.class,
IProgressListener.class, IProgressListener.OperationCanceledException.class,
Severity.class, Messages.class, };
execJar = makeJar(jarname, "Main-Class: ", classesNames, classes); //$NON-NLS-1$
}
return execJar;
}
}