blob: 8775e4add513bfedb35e8bef060c0a6b3d4af0f5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2018 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 to use reflection for non-standard classes
* IBM Corporation/Andrew Johnson - Improved exception handling and hprof support
*******************************************************************************/
package org.eclipse.mat.ibmvm.acquire;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.query.annotations.Help;
import org.eclipse.mat.query.annotations.HelpUrl;
import org.eclipse.mat.query.annotations.Name;
import org.eclipse.mat.snapshot.acquire.VmInfo;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.IProgressListener.Severity;
//import com.ibm.tools.attach.AgentInitializationException;
//import com.ibm.tools.attach.AgentLoadException;
//import com.ibm.tools.attach.AgentNotSupportedException;
//import com.ibm.tools.attach.AttachOperationFailedException;
//import com.ibm.tools.attach.VirtualMachine;
//import com.ibm.tools.attach.VirtualMachineDescriptor;
//import com.ibm.tools.attach.spi.AttachProvider;
/**
* Base class for generating dumps on IBM VMs.
* This class uses reflection to call the com.ibm or com.sun classes.
* Be sure to update IBMExecDumpProvider.getExecJar() when any classes are added here.
* @author ajohnson
*
*/
@Name("IBM Dump (using attach API)")
@Help("help for IBM Dump (using attach API)")
@HelpUrl("/org.eclipse.mat.ui.help/tasks/acquiringheapdump.html#task_acquiringheapdump__2")
public class IBMDumpProvider extends BaseProvider
{
/**
* Wrapper class for com.ibm.tools.attach/com.sun.tools.attach version.
*/
static class AgentLoadException extends Exception
{
private static final long serialVersionUID = 1L;
/**
* Construct the exception from the
* com.ibm.tools.attach.AgentLoadException or
* com.sun.tools.attach.AgentLoadException object.
*
* @param e
*/
AgentLoadException(Throwable e)
{
super(e.getMessage());
initCause(e);
}
}
/**
* Wrapper class for com.ibm.tools.attach/com.sun.tools.attach version.
*/
static class AgentInitializationException extends Exception
{
private static final long serialVersionUID = 1L;
/*
* Construct the exception from the
* com.ibm.tools.attach.AgentInitializationException or
* com.sun.tools.attach.AgentInitializationException object.
*/
AgentInitializationException(Throwable e)
{
super(e.getMessage()+" returnValue="+VirtualMachine.call(e, "returnValue"));
initCause(e);
}
int returnValue()
{
return (Integer)VirtualMachine.call(getCause(), "returnValue");
}
}
/**
* Wrapper class for com.ibm.tools.attach/com.sun.tools.attach version.
*/
static class AttachNotSupportedException extends Exception
{
private static final long serialVersionUID = 1L;
/**
* Construct the exception from the
* com.ibm.tools.attach.AttachNotSupportedException or
* com.sun.tools.attach.AttachNotSupportedException object.
*
* @param e
*/
AttachNotSupportedException(Throwable e)
{
super(e.getMessage());
initCause(e);
}
}
/**
* Wrapper class for com.ibm.tools.attach/com.sun.tools.attach version.
*/
static class AttachOperationFailedException extends IOException
{
/**
* Wrapper class for com.ibm.tools.attach/com.sun.tools.attach version.
*/
private static final long serialVersionUID = 1L;
/**
* Construct the exception from the
* com.ibm.tools.attach.AttachNotSupportedException or
* com.sun.tools.attach.AttachNotSupportedException object.
*
* @param e
*/
AttachOperationFailedException (Throwable e)
{
super(e.getMessage());
initCause(e);
}
}
static class VirtualMachineDescriptor
{
String name;
String id;
String displayName;
/** A wrapper version of the provider */
AttachProvider pr;
/** The wrapped object */
Object vmd;
/**
* Construct the exception from the
* com.ibm.tools.attach.VirtualMachineDescriptor or
* com.sun.tools.attach.VirtualMachineDescriptor object.
*
* @param vmd
* The object to wrap.
*/
VirtualMachineDescriptor(Object vmd)
{
this.vmd = vmd;
this.pr = new AttachProvider(VirtualMachine.call(vmd, "provider"));
this.id = (String) VirtualMachine.call(vmd, "id");
this.displayName = (String) (String) VirtualMachine.call(vmd, "displayName");
}
String displayName()
{
return displayName;
}
String id()
{
return id;
}
AttachProvider provider()
{
return pr;
}
}
/**
* Wrapper class for com.ibm.tools.attach/com.sun.tools.attach version.
*/
static class AttachProvider
{
/** The wrapper object */
Object ap;
/**
* Construct a wrapper instance from from the com.ibm or com.sun
* version: com.ibm.tools.attach.AttachProvider or
* com.sun.tools.attach.AttachProvider
*
* @param o
* the object to wrap
*/
AttachProvider(Object o)
{
ap = o;
}
/**
* Attach to the virtual machine
*
* @param vmd
* A wrapped version of the descriptor
* @return a wrapped version of the VirtualMachine
* @throws IOException
*/
VirtualMachine attachVirtualMachine(VirtualMachineDescriptor vmd)
throws IOException, AttachNotSupportedException
{
Object o;
try
{
o = VirtualMachine.call(ap, "attachVirtualMachine", vmd.vmd);
}
catch (UndeclaredThrowableException e)
{
Throwable t = e.getCause();
// Change the type
if (VirtualMachine.isSubclassOf(t, "AttachNotSupportedException"))
{
throw new AttachNotSupportedException(t);
}
if (VirtualMachine.isSubclassOf(t, "AttachOperationFailedException"))
{
throw new AttachOperationFailedException(t);
}
if (t instanceof IOException)
{
if (t.getMessage().contains("not attach to current VM"))
{
// Java 9/10 throws IOException instead of more useful AttachNotSupportedException
throw new AttachNotSupportedException(t);
}
throw (IOException)t;
}
throw e;
}
return new VirtualMachine(o);
}
String name()
{
return (String) VirtualMachine.call(ap, "name");
}
String type()
{
return (String) VirtualMachine.call(ap, "type");
}
private static Class<?>attCls;
private static Class<?> getStaticClass() throws LinkageError
{
if (attCls == null)
{
attCls = VirtualMachine.getClass("com.ibm.tools.attach.spi.AttachProvider", "com.sun.tools.attach.spi.AttachProvider");
}
return attCls;
}
static List<AttachProvider>providers()
{
Class<?>apc = getStaticClass();
List<?> l = (List<?>)VirtualMachine.call(apc, "providers");
List<AttachProvider>ret = new ArrayList<AttachProvider>(l.size());
for (Object o : l)
{
AttachProvider ap = new AttachProvider(o);
ret.add(ap);
}
return ret;
}
List<VirtualMachineDescriptor>listVirtualMachines()
{
List<?> l = (List<?>)VirtualMachine.call(ap, "listVirtualMachines");
List<VirtualMachineDescriptor>ret = new ArrayList<VirtualMachineDescriptor>(l.size());
for (Object o : l)
{
VirtualMachineDescriptor vmd = new VirtualMachineDescriptor(o);
ret.add(vmd);
}
return ret;
}
public String toString()
{
return name()+ " " + type();
}
}
/**
* Wrapper class for com.ibm.tools.attach/com.sun.tools.attach version.
*/
static class VirtualMachine
{
/**
* Helper for converting exceptions.
*
* @param e
* The exception
* @param classname
* Detect if e or a superclass class has this name.
* @return
*/
static boolean isSubclassOf(Throwable e, String classname)
{
for (Class<?> o = e.getClass(); o != null; o = o.getSuperclass())
{
if (o.getSimpleName().equals(classname)) { return true; }
}
return false;
}
/**
* Helper to call a method via reflection.
*
* @param o
* The object or null for static methods.
* @param method
* The method name
* @param args
* The arguments
* @return
*/
static Object call(Object o, String method, Object... args)
{
// Find the argument types.
Class<?> types[] = new Class[args.length];
for (int i = 0; i < args.length; ++i)
{
types[i] = args[i] != null ? args[i].getClass() : null;
}
Method m = null;
Method ms[];
Class<? extends Object> cls;
// Presume a class object is for a static method
if (o instanceof Class)
{
cls = ((Class<?>) o);
}
else
{
cls = o.getClass();
}
// Find a public class we can call methods from.
// The ibm. is to exclude IBM Java 9 classes which are public but not accessible
while (!Modifier.isPublic(cls.getModifiers()) || cls.getPackage().getName().startsWith("ibm.") || cls.getPackage().getName().startsWith("sun."))
{
cls = cls.getSuperclass();
}
ms = cls.getMethods();
// Don't worry about interfaces for the moment
l: for (Method m1 : ms)
{
int mods = m1.getModifiers();
if (m1.getName().equals(method) && m1.getParameterTypes().length == types.length
&& Modifier.isPublic(mods) && Modifier.isPublic(m1.getDeclaringClass().getModifiers()))
{
// Match the parameters
for (int i = 0; i < types.length; ++i)
{
Class<?> t = m1.getParameterTypes()[i];
if (types[i] != null && !t.isAssignableFrom(types[i]))
{
continue l;
}
}
if (m == null)
{
m = m1;
}
else
{
// duplicate, so uncertain
m = null;
break;
}
}
}
// Not found or duplicate, so do direct search and generate error if
// needed
if (m == null)
{
try
{
if (o instanceof Class)
{
m = ((Class<?>) o).getMethod(method, types);
}
else
{
// This might not work if the argument is a superclass,
// or the parameter is an interface
m = o.getClass().getMethod(method, types);
}
}
catch (NoSuchMethodException e)
{
// Fix up the error
LinkageError l = new LinkageError();
l.initCause(e);
throw l;
}
}
Object ret;
try
{
ret = m.invoke(o, args);
}
catch (IllegalAccessException e)
{
IllegalArgumentException e2 = new IllegalArgumentException("Object:" + o + " method:" + m + " exception:" + e);
e2.initCause(e);
throw e2;
}
catch (InvocationTargetException e)
{
if (e.getCause() instanceof RuntimeException)
{
throw (RuntimeException)e.getCause();
}
else if (e.getCause() instanceof Error)
{
throw (Error)e.getCause();
}
else
{
throw new UndeclaredThrowableException(e.getCause());
}
}
return ret;
}
/** The object which has been wrapped */
Object vm;
/**
* Wrap a com.ibm.tools.attach.VirtualMachine or
* com.sun.tools.attach.VirtualMachine object
*
* @param o
*/
VirtualMachine(Object o)
{
vm = o;
}
/**
* List all the VMs
*
* @return a list of wrapped versions of the VMs
*/
static List<VirtualMachineDescriptor> list()
{
Class<?> c1 = getStaticClass();
List<?> l = (List<?>) call(c1, "list");
List<VirtualMachineDescriptor> ret = new ArrayList<VirtualMachineDescriptor>();
for (Object o : l)
{
VirtualMachineDescriptor vmd1 = new VirtualMachineDescriptor(o);
ret.add(vmd1);
}
return ret;
}
/**
* Load an agent into the VM
*
* @param jar
* @param command
* @throws IOException
* @throws AgentLoadException
* @throws AgentInitializationException
*/
public void loadAgent(String jar, String command)
throws IOException, AgentLoadException, AgentInitializationException
{
try
{
call(vm, "loadAgent", jar, command);
}
catch (UndeclaredThrowableException e)
{
Throwable t = e.getCause();
// Change the type
if (isSubclassOf(t, "AgentLoadException")) { throw new AgentLoadException(t); }
if (isSubclassOf(t, "AgentInitializationException")) { throw new AgentInitializationException(t); }
if (t instanceof IOException) { throw (IOException)t; }
// Rethrow
throw e;
}
}
/**
* Load an agent into the VM
*
* @param lib executable library
* @param command to pass to the library
* @throws IOException if there is a problem with communication
* @throws AgentLoadException1 if the library cannot be loaded
* @throws AgentInitializationException1 if the command does not run properly
*/
public void loadAgentLibrary(String lib, String command)
throws IOException, AgentLoadException, AgentInitializationException
{
try
{
call(vm, "loadAgentLibrary", lib, command);
}
catch (UndeclaredThrowableException e)
{
Throwable t = e.getCause();
// Change the type
if (isSubclassOf(t, "AgentLoadException")) { throw new AgentLoadException(t); }
if (isSubclassOf(t, "AgentInitializationException")) { throw new AgentInitializationException(t); }
if (t instanceof IOException) { throw (IOException)t; }
// Rethrow
throw e;
}
}
static VirtualMachine attach(String nm) throws IOException, AttachNotSupportedException
{
Class<?> c1 = getStaticClass();
Object o;
try
{
o = call(c1, "attach", nm);
}
catch (UndeclaredThrowableException e)
{
Throwable t = e.getCause();
if (isSubclassOf(t, "AttachNotSupportedException"))
{
throw new AttachNotSupportedException(t);
}
if (t instanceof IOException)
{
if (t.getMessage().contains("not attach to current VM"))
{
// Java 9/10 throws IOException instead of more useful AttachNotSupportedException
throw new AttachNotSupportedException(t);
}
throw (IOException)t;
}
throw e;
}
return new VirtualMachine(o);
}
private static Class<?>clsVM;
/**
* Find out which version of the real class we are using.
*
* @return
* @throws LinkageError
*/
private static Class<?> getStaticClass() throws LinkageError
{
if (clsVM == null)
{
clsVM = getClass("com.ibm.tools.attach.VirtualMachine", "com.sun.tools.attach.VirtualMachine");
}
return clsVM;
}
static URLClassLoader urlcl;
static Class<?> getClass(String cn1, String cn2) throws LinkageError
{
Class<?> c1;
try
{
c1 = Class.forName(cn1);
}
catch (ClassNotFoundException e)
{
try
{
c1 = Class.forName(cn2);
}
catch (ClassNotFoundException e2)
{
/**
* Oracle-based VMs don't have com.sun.tools.attach classes on the
* standard class path, even for JDKs.
* We try looking for the classes here.
*/
File f = new File(System.getProperty("java.home"));
f = f.getParentFile();
if (f != null)
{
f = new File(f, "lib");
f = new File(f, "tools.jar");
if (f.canRead())
{
try
{
if (urlcl == null)
{
urlcl = new URLClassLoader(new URL[] {f.toURI().toURL()});
}
try
{
return urlcl.loadClass(cn2);
}
catch (ClassNotFoundException e1)
{
}
}
catch (MalformedURLException e1)
{
}
}
}
LinkageError l = new LinkageError();
l.initCause(e2);
throw l;
}
}
return c1;
}
Properties getAgentProperties() throws IOException
{
try
{
return (Properties) call(vm, "getAgentProperties");
}
catch (UndeclaredThrowableException e)
{
Throwable t = e.getCause();
if (t instanceof IOException) throw (IOException)t;
throw e;
}
}
Properties getSystemProperties() throws IOException
{
try
{
return (Properties) call(vm, "getSystemProperties");
}
catch (UndeclaredThrowableException e)
{
Throwable t = e.getCause();
if (t instanceof IOException) throw (IOException)t;
throw e;
}
}
void detach() throws IOException
{
try
{
call(vm, "detach");
}
catch (UndeclaredThrowableException e)
{
Throwable t = e.getCause();
if (t instanceof IOException) throw (IOException)t;
throw e;
}
}
}
/**
* Helper class to load an agent (blocking call)
* allowing the main thread to monitor its progress
* @author ajohnson
*
*/
private static final class AgentLoader extends Thread implements AgentLoader2
{
private final String jar;
private final VirtualMachine vm;
private final String command;
private AgentLoadException e1;
private AgentInitializationException e2;
private IOException e3;
private boolean fail;
private AgentLoader(String jar, VirtualMachine vm, String command)
{
this.jar = jar;
this.vm = vm;
this.command = command;
}
/* (non-Javadoc)
* @see org.eclipse.mat.ibmvm.acquire.AgentLoader2#run()
*/
public void run() {
try
{
vm.loadAgent(jar, command);
}
catch (AgentLoadException e2)
{
this.e1 = e2;
setFailed();
}
catch (AgentInitializationException e)
{
this.e2 = e;
setFailed();
}
catch (IOException e3)
{
this.e3 = e3;
setFailed();
}
}
/* (non-Javadoc)
* @see org.eclipse.mat.ibmvm.acquire.AgentLoader2#failed()
*/
public synchronized boolean failed()
{
return fail;
}
private synchronized void setFailed()
{
fail = true;
}
/* (non-Javadoc)
* @see org.eclipse.mat.ibmvm.acquire.AgentLoader2#throwFailed(org.eclipse.mat.util.IProgressListener)
*/
public void throwFailed(IProgressListener listener) throws SnapshotException, IOException
{
if (e1 != null)
{
listener.sendUserMessage(Severity.WARNING, Messages.getString("IBMDumpProvider.AgentLoad"), e1); //$NON-NLS-1$
throw new SnapshotException(Messages.getString("IBMDumpProvider.AgentLoad"), e1); //$NON-NLS-1$
}
if (e2 != null)
{
listener.sendUserMessage(Severity.WARNING, Messages.getString("IBMDumpProvider.AgentInitialization"), e2); //$NON-NLS-1$
throw new SnapshotException(Messages.getString("IBMDumpProvider.AgentInitialization"), e2); //$NON-NLS-1$
}
if (e3 != null)
{
throw e3;
}
}
}
/**
* Find new files not ones we know about
*/
private static final class NewFileFilter implements FileFilter
{
private final Collection<File> previousFiles;
private NewFileFilter(Collection<File> previousFiles)
{
this.previousFiles = previousFiles;
}
public boolean accept(File f)
{
return !previousFiles.contains(f);
}
}
/**
* Indicate progress back to starting process
* @author ajohnson
*
*/
private static final class StderrProgressListener implements IProgressListener
{
public void beginTask(String name, int totalWork)
{}
public void done()
{}
public boolean isCanceled()
{
return false;
}
public void sendUserMessage(Severity severity, String message, Throwable exception)
{}
public void setCanceled(boolean value)
{}
public void subTask(String name)
{}
public void worked(int work)
{
for (int i = 0; i < work; ++i)
{
System.err.print('.');
}
}
}
/**
* sorter for files by date modified
*/
private static final class FileComparator implements Comparator<File>, Serializable
{
/**
*
*/
private static final long serialVersionUID = -3725792252276130382L;
public int compare(File f1, File f2)
{
return Long.valueOf(f1.lastModified()).compareTo(Long.valueOf(f2.lastModified()));
}
}
public IBMDumpProvider()
{
// 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;
}
}
/**
* Suggested name for dumps of this type
* @return example dump name
*/
String dumpName()
{
return new File("ibmdump.dmp").getAbsolutePath(); //$NON-NLS-1$
}
private static File agentJar;
/**
* Number of files generated by this dump type
* @return the number of files, often just 1 or 2 (e.g. javacore + phd).
*/
int files()
{
return 1;
}
/**
* Post process a generated dump
* @param preferredDump where the final dump should be put
* @param compress Whether to compress/zip the dump
* @param dumps The dump files
* @param udir The directory where the dump files were generated
* @param javahome The Java home directory of the process which produced the dump
* @param listener to show progress
* @return the result of post-processing the dump
* @throws IOException
* @throws InterruptedException
* @throws SnapshotException
*/
File jextract(File preferredDump, boolean compress, List<File>dumps, File udir, File javahome, IProgressListener listener)
throws IOException, InterruptedException, SnapshotException
{
File original = dumps.get(0);
if (original.renameTo(preferredDump))
{
return preferredDump;
}
else
{
// @TODO consider java.nio.file.Files.move when we move to Java 1.7
return original;
}
}
/**
* Average file length for a group of files.
* @param files
* @return
*/
long averageFileSize(Collection<File> files)
{
long l = 0;
int i = 0;
for (File f : files)
{
if (f.isFile())
{
l += f.length();
++i;
}
}
return l / i;
}
/*
* (non-Javadoc)
* @see org.eclipse.mat.snapshot.acquire.IHeapDumpProvider#acquireDump(org.eclipse.mat.snapshot.acquire.VmInfo, java.io.File, org.eclipse.mat.util.IProgressListener)
*/
public File acquireDump(VmInfo info, File preferredLocation, IProgressListener listener) throws SnapshotException
{
IBMVmInfo vminfo = (IBMVmInfo)info;
IBMDumpProvider helper = getDumpProvider(vminfo);
// Delegate to the appropriate helper
if (helper != this) return helper.acquireDump(info, preferredLocation, listener);
listener.beginTask(Messages.getString("IBMDumpProvider.GeneratingDump"), TOTAL_WORK); //$NON-NLS-1$
try
{
listener.subTask(MessageFormat.format(Messages.getString("IBMDumpProvider.AttachingToVM"), vminfo.getPidName())); //$NON-NLS-1$
final VirtualMachine vm = VirtualMachine.attach(vminfo.getPidName());
try
{
Properties props = vm.getSystemProperties();
String javah = props.getProperty("java.home", System.getProperty("java.home")); //$NON-NLS-1$ //$NON-NLS-2$
File javahome = new File(javah);
// Where the dumps end up
// %IBM_HEAPDUMPDIR%
// %IBM_JAVACOREDIR%
// %IBM_COREDIR%
// pwd
// TMPDIR
// /tmp
// /temp
// user.dir
// %LOCALAPPDATA%/VirtualStore/Program Files (x86)
File udir;
if (vminfo.dumpdir == null)
{
udir = null;
if (vminfo.type == DumpType.HPROF)
{
// If we do an HPROF dump we can put it directly where it is needed
udir = preferredLocation.getParentFile();
}
if (udir == null)
{
String userdir = guessDumpLocation(props);
udir = new File(userdir);
}
// Set the directory, so even it is fails the user could adjust it
vminfo.dumpdir = udir;
//System.err.println("Setting dumpdir "+udir);
}
else
{
udir = vminfo.dumpdir;
}
File f1[] = udir.listFiles();
if (f1 == null)
{
throw new FileNotFoundException(udir.getPath());
}
Collection<File> previous = new HashSet<File>(Arrays.asList(f1));
long avg = averageFileSize(previous);
//System.err.println("Average = " + avg);
final String jar = getAgentJar().getAbsolutePath();
listener.subTask(Messages.getString("IBMDumpProvider.StartingAgent")); //$NON-NLS-1$
AgentLoader2 t = new AgentLoader(jar, vm, vminfo.agentCommand(new File(udir, preferredLocation.getName())));
t.start();
List<File> newFiles = progress(udir, previous, files(), avg, t, listener);
if (listener.isCanceled())
{
t.interrupt();
return null;
}
if (t.failed())
{
t.throwFailed(listener);
}
listener.done();
if (newFiles.isEmpty())
{
String msg = MessageFormat.format(Messages.getString("IBMDumpProvider.UnableToFindDump"), udir.getAbsoluteFile());
throw new FileNotFoundException(msg);
}
// Consider moving to after the detach
return jextract(preferredLocation, vminfo.compress, newFiles, udir, javahome, listener);
}
catch (InterruptedException e)
{
listener.sendUserMessage(Severity.WARNING, Messages.getString("IBMDumpProvider.Interrupted"), e); //$NON-NLS-1$
throw new SnapshotException(Messages.getString("IBMDumpProvider.Interrupted"), e); //$NON-NLS-1$
}
finally {
vm.detach();
}
}
catch (IOException e)
{
listener.sendUserMessage(Severity.WARNING, Messages.getString("IBMDumpProvider.UnableToGenerateDump"), e); //$NON-NLS-1$
throw new SnapshotException(Messages.getString("IBMDumpProvider.UnableToGenerateDump"), e); //$NON-NLS-1$
}
catch (SnapshotException e)
{
throw e;
}
catch (AttachNotSupportedException e)
{
info.setHeapDumpEnabled(false);
String msg = MessageFormat.format(Messages.getString("IBMDumpProvider.UnsuitableVM"), info.toString());
listener.sendUserMessage(Severity.WARNING, msg, e); //$NON-NLS-1$
throw new SnapshotException(msg, e); //$NON-NLS-1$
}
}
/**
* Update the progress bar for created files
* @param udir directory where files are created
* @param previous
* @param nfiles
* @param loader Thread which has loaded the agent jar
* @throws FileNotFoundException
*/
private List<File> progress(File udir, Collection<File> previous, int nfiles, long avg, AgentLoader2 loader, IProgressListener listener)
throws InterruptedException, FileNotFoundException
{
listener.subTask(Messages.getString("IBMDumpProvider.WaitingForDumpFiles")); //$NON-NLS-1$
List<File> newFiles = new ArrayList<File>();
// Wait up to 30 seconds for a file to be created and written to
long l = 0;
int worked = 0;
long start = System.currentTimeMillis(), t;
for (int i = 0; (l = fileLengths(udir, previous, newFiles, nfiles)) == 0
&& i < CREATE_COUNT && (t = System.currentTimeMillis()) < start + CREATE_COUNT*SLEEP_TIMEOUT; ++i)
{
Thread.sleep(SLEEP_TIMEOUT);
if (listener.isCanceled() || loader.failed())
return null;
int towork = (int)Math.min(((t - start) / SLEEP_TIMEOUT), CREATE_COUNT);
listener.worked(towork - worked);
worked = towork;
}
listener.worked(CREATE_COUNT - worked);
worked = CREATE_COUNT;
// Wait for FINISHED_TIMEOUT seconds after file length stops changing
long l0 = l - 1;
int iFile = 0;
start = System.currentTimeMillis();
for (int i = 0, j = 0; ((l = fileLengths(udir, previous, newFiles, nfiles)) != l0
|| j++ < FINISHED_COUNT || newFiles.size() > iFile)
&& i < GROW_COUNT
&& (t = System.currentTimeMillis()) < start + GROW_COUNT*SLEEP_TIMEOUT; ++i)
{
while (iFile < newFiles.size())
{
listener.subTask(MessageFormat.format(Messages.getString("IBMDumpProvider.WritingFile"), newFiles.get(iFile++))); //$NON-NLS-1$
}
if (l0 != l)
{
j = 0;
int towork = (int) (l * GROWING_COUNT / avg);
listener.worked(towork - worked);
worked = towork;
l0 = l;
}
Thread.sleep(SLEEP_TIMEOUT);
if (listener.isCanceled() || loader.failed())
return null;
listener.worked(1);
}
// Remove files which no longer exist
for (Iterator<File>it = newFiles.iterator(); it.hasNext();)
{
File f = it.next();
if (!f.exists())
it.remove();
}
return newFiles;
}
private static synchronized File getAgentJar() throws IOException
{
if (agentJar == null || !agentJar.canRead())
{
agentJar = makeAgentJar();
}
return agentJar;
}
private static File makeAgentJar() throws IOException, FileNotFoundException
{
String jarname = "org.eclipse.mat.ibmdumps"; //$NON-NLS-1$
String agents[] = { "org.eclipse.mat.ibmvm.agent.DumpAgent" }; //$NON-NLS-1$
Class<?> cls[] = new Class<?>[0];
return makeJar(jarname, "Agent-class: ", agents, cls); //$NON-NLS-1$
}
/**
* Find the new files in a directory
*
* @param udir
* The directory
* @param previousFiles
* File that we already know exist in the directory
* @param newFiles
* newly discovered files, in discovery/modification order
* @return a list of new files in the directory
* @throws FileNotFoundException
*/
List<File> files(File udir, final Collection<File> previousFiles, List<File> newFiles) throws FileNotFoundException
{
File f2[] = udir.listFiles(new NewFileFilter(previousFiles));
if (f2 == null)
{
throw new FileNotFoundException(udir.getPath());
}
List<File> new2 = Arrays.asList(f2);
// Sort the new files in order of modification
Collections.sort(new2, new FileComparator());
previousFiles.addAll(new2);
newFiles.addAll(new2);
return newFiles;
}
long fileLengths(File udir, Collection<File> previous, List<File> newFiles, int maxFiles) throws FileNotFoundException
{
Collection<File> nw = files(udir, previous, newFiles);
long l = 0;
int i = 0;
for (File f : nw)
{
if (!f.exists())
{
// File has disappeared (e.g. on Linux renamed from core to core.????)
// Just skip it and don't include in the count
continue;
}
if (++i > maxFiles)
break;
l += f.length();
}
return l;
}
/**
* @see org.eclipse.mat.snapshot.acquire.IHeapDumpProvider#getAvailableVMs(org.eclipse.mat.util.IProgressListener)
*/
public List<IBMVmInfo> getAvailableVMs(IProgressListener listener)
{
try
{
return getAvailableVMs1(listener);
}
catch (LinkageError e)
{
return null;
}
}
/**
* List available VMs.
* work done calculated as below:
* T = 10
* N = 100
* X->1..100
*
* Step p = x*T/N - (x-1)*T/N
*
* @param listener
* @return a list of VMs
*/
private List<IBMVmInfo> getAvailableVMs1(IProgressListener listener)
{
int totalwork = 24;
int provwork = 4;
listener.beginTask(Messages.getString("IBMDumpProvider.ListingIBMVMs"), totalwork); //$NON-NLS-1$
int y = 0;
int vmcount = VirtualMachine.list().size();
int x = 0;
List<IBMVmInfo> jvms = new ArrayList<IBMVmInfo>();
List<AttachProvider> provs = AttachProvider.providers();
int provcount = provs.size();
for (AttachProvider prov : provs)
{
listener.subTask(MessageFormat.format(Messages.getString("IBMDumpProvider.ListingFirst"), prov.name())); //$NON-NLS-1$
List<VirtualMachineDescriptor> list = prov.listVirtualMachines();
y++;
int workp = y * provwork / provcount - (y - 1) * provwork / provcount;
listener.worked(workp);
listener.subTask(MessageFormat.format(Messages.getString("IBMDumpProvider.ListingDetails"), prov.name())); //$NON-NLS-1$
for (VirtualMachineDescriptor vmd : list)
{
IBMVmInfo ifo = getVmInfo(vmd);
jvms.add(ifo);
++x;
int workv = x * (totalwork - provwork) / vmcount - (x - 1) * (totalwork - provwork) / vmcount;
listener.worked(workv);
if (listener.isCanceled())
{
// If the user cancelled then perhaps the attach is hanging
listAttach = false;
break;
}
}
}
listener.done();
return jvms;
}
private IBMVmInfo getVmInfo(VirtualMachineDescriptor vmd)
{
boolean usable = true;
String unusableCause = "";
String dir = null;
// See if the VM is usable to get dumps
String displayName = vmd.displayName();
if ((vmd.id().equals(displayName) || "".equals(displayName)) && listAttach)
{
// Insufficient details of running VM, so attach for more information
try
{
// Hope that this is not too intrusive to the target
VirtualMachine vm = vmd.provider().attachVirtualMachine(vmd);
try
{
Properties p = vm.getSystemProperties();
dir = p.getProperty("user.dir");
// Get something which might identify the running VM to
// the user
displayName = p.getProperty("java.class.path");
if (displayName == null || displayName.equals(""))
{
displayName = dir;
}
dir = guessDumpLocation(p);
}
finally
{
try
{
vm.detach();
}
catch (NullPointerException e)
{
// Ignore from IBM Java 9
}
}
// See if loading an agent would fail
// Java 5 SR10 and SR11 don't have a loadAgent method, so find
// out now
try
{
vm.loadAgent((String)null, (String)null);
}
catch (AgentLoadException e)
{
}
catch (AgentInitializationException e)
{
}
catch (LinkageError e)
{
usable = false;
unusableCause = e.getLocalizedMessage();
}
catch (NullPointerException e)
{
// Ignore
}
catch (IOException e)
{
// Ignore, expect an IOException if the method exists as the VM is detached
}
}
catch (IOException e)
{
usable = false;
unusableCause = e.getLocalizedMessage();
}
catch (AttachNotSupportedException e)
{
usable = false;
unusableCause = e.getLocalizedMessage();
}
}
// Create VMinfo to generate heap dumps
String desc = MessageFormat.format(Messages.getString("IBMDumpProvider.VMDescription"), vmd.provider().name(), vmd.provider().type(), displayName); //$NON-NLS-1$
if (!usable)
{
desc = unusableCause + " : " + desc;
}
IBMVmInfo ifo = new IBMVmInfo(vmd.id(), desc, usable, null, this);
if (vmd.provider().name().equals("sun"))
{
ifo.type = DumpType.HPROF;
// No need for a dump directory - HPROF can dump directly to the required location
dir = null;
}
else
{
ifo.type = defaultType;
}
ifo.live = defaultLive;
ifo.compress = defaultCompress;
if (dir != null)
ifo.dumpdir = new File(dir);
ifo.setHeapDumpEnabled(usable);
return ifo;
}
private String guessDumpLocation(Properties props)
{
String dir = props.getProperty("user.dir", System.getProperty("user.dir")); //$NON-NLS-1$ //$NON-NLS-2$
// If there is a system trace file perhaps the dumps also do there
String tracefilename = props.getProperty("system.trace.file"); //$NON-NLS-1$
if (tracefilename != null)
{
File tracefile = (new File(tracefilename));
File tdir = tracefile.getParentFile();
if (tdir != null)
{
File tdira = tdir.getAbsoluteFile();
if (tdir.equals(tdira))
{
// Must be an absolute path, because otherwise could be different
// when examined from this process
dir = tdir.getPath();
}
}
}
return dir;
}
/**
* Lists VMs or acquires a dump.
* Used when attach API not usable from the MAT process.
*
* @param s <ul><li>[0] dump type (HEAP=heap+java,SYSTEM=system,JAVA=java)</li>
* <li>[1] VM id = PID</li>
* <li>[2] true/false live objects only in dump</li>
* <li>[3] true/false compress dump</li>
* <li>[4] dump name</li>
* <li>[5] dump directory (optional)</li>
* </ul>
* List VMs
* <ul>
* <li>true - attach to VM to get more details</li>
* </ul>
* Output<ul>
* <li>dump filename</li>
* <li>or list of all processes (if argument list is empty)
* <pre>PID;proposed file name;directory;enable dump;description</pre></li>
* </ul>
*/
public static void main(String s[]) throws Exception
{
IBMDumpProvider prov = new IBMDumpProvider();
if (s.length < 5 && s.length > 0)
{
prov.listAttach = Boolean.parseBoolean(s[0]);
}
IProgressListener ii = new StderrProgressListener();
List<IBMVmInfo> vms = prov.getAvailableVMs1(ii);
for (VmInfo info : vms)
{
IBMVmInfo vminfo = (IBMVmInfo)info;
String vm = vminfo.getPidName();
String dir = vminfo.dumpdir != null ? vminfo.dumpdir.getAbsolutePath() : "";
String proposedFile = info.getProposedFileName();
// Let the file be determined later
proposedFile = "";
String vm2 = vm + INFO_SEPARATOR + info.isHeapDumpEnabled() + INFO_SEPARATOR + vminfo.type + INFO_SEPARATOR + proposedFile + INFO_SEPARATOR + dir + INFO_SEPARATOR + info.getDescription();
if (s.length < 5)
{
System.out.println(vm2);
}
else
{
if (vm.equals(s[1]))
{
DumpType tp = DumpType.valueOf(s[0]);
vminfo.type = tp;
vminfo.live = Boolean.parseBoolean(s[2]);
vminfo.compress = Boolean.parseBoolean(s[3]);
if (s.length > 5)
{
vminfo.dumpdir = new File(s[5]);
}
File f2 = vminfo.getHeapDumpProvider().acquireDump(info, new File(s[4]), ii);
System.out.println(f2.getAbsolutePath());
return;
}
}
}
if (s.length > 1)
{
throw new IllegalArgumentException(MessageFormat.format(Messages.getString("IBMDumpProvider.NoVMFound"), s[1])); //$NON-NLS-1$
}
}
IBMDumpProvider getDumpProvider(IBMVmInfo info)
{
if (getClass() != IBMDumpProvider.class)
return this;
else if (info.type == DumpType.SYSTEM)
return new IBMSystemDumpProvider();
else if (info.type == DumpType.HEAP)
return new IBMHeapDumpProvider();
else if (info.type == DumpType.JAVA)
return new IBMJavaDumpProvider();
else if (info.type == DumpType.HPROF)
return new HprofDumpProvider();
return this;
}
/**
* Update date and time stamps in suggested file name from actual file
* @param preferredDump
* @param actual
* @return
*/
File mergeFileNames(File preferredDump, File actual)
{
String fn1 = preferredDump.getName();
String fn1a = fn1.replaceAll("\\d", "#"); //$NON-NLS-1$//$NON-NLS-2$
String fn2 = actual.getName();
String fn2a = fn2.replaceAll("\\d", "#"); //$NON-NLS-1$//$NON-NLS-2$
fn2a = fn2a.substring(0, fn2a.lastIndexOf('#') + 1);
int fi = fn1a.indexOf(fn2a);
File ret;
if (fi >= 0)
{
String newfn = fn1.substring(0, fi) + fn2.substring(0, fn2a.length()) + fn1.substring(fi + fn2a.length());
ret = new File(preferredDump.getParentFile(), newfn);
}
else
{
ret = preferredDump;
}
return ret;
}
}