mirror of
https://github.com/alibaba/arthas.git
synced 2024-04-21 10:21:39 +00:00
add vmtool command, part1. #1781
This commit is contained in:
@@ -2,8 +2,6 @@ package arthas;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.taobao.arthas.common.OSUtils;
|
||||
|
||||
/**
|
||||
* @author ZhangZiCheng 2021-02-12
|
||||
* @author hengyunabc 2021-04-26
|
||||
@@ -16,24 +14,6 @@ public class VmTool implements VmToolMXBean {
|
||||
*/
|
||||
public final static String JNI_LIBRARY_NAME = "ArthasJniLibrary";
|
||||
|
||||
private static String libName = null;
|
||||
static {
|
||||
if (OSUtils.isMac()) {
|
||||
libName = "libArthasJniLibrary-x64.dylib";
|
||||
}
|
||||
if (OSUtils.isLinux()) {
|
||||
libName = "libArthasJniLibrary-x64.so";
|
||||
if (OSUtils.isArm32()) {
|
||||
libName = "libArthasJniLibrary-arm.so";
|
||||
} else if (OSUtils.isArm64()) {
|
||||
libName = "libArthasJniLibrary-aarch64.so";
|
||||
}
|
||||
}
|
||||
if (OSUtils.isWindows()) {
|
||||
libName = "libArthasJniLibrary-x64.dll";
|
||||
}
|
||||
}
|
||||
|
||||
private static VmTool instance;
|
||||
|
||||
private VmTool() {
|
||||
@@ -58,10 +38,6 @@ public class VmTool implements VmToolMXBean {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static String detectLibName() {
|
||||
return libName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测jni-lib是否正常,如果正常,应该输出OK
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* <pre>
|
||||
* 修改后要同步到 spy/src/main/java 。
|
||||
* </pre>
|
||||
*/
|
||||
package arthas;
|
||||
@@ -2,6 +2,8 @@ package arthas;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.taobao.arthas.common.VmToolUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
@@ -25,7 +27,7 @@ public class VmToolTest {
|
||||
String path = VmTool.class.getProtectionDomain().getCodeSource().getLocation().getPath();
|
||||
System.err.println(path);
|
||||
|
||||
String libPath = new File(path, VmTool.detectLibName()).getAbsolutePath();
|
||||
String libPath = new File(path, VmToolUtils.detectLibName()).getAbsolutePath();
|
||||
VmTool vmtool = VmTool.getInstance(libPath);
|
||||
|
||||
//调用native方法,获取已加载的类,不包括小类型(如int)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.taobao.arthas.common;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hengyunabc 2021-04-27
|
||||
*
|
||||
*/
|
||||
public class VmToolUtils {
|
||||
private static String libName = null;
|
||||
static {
|
||||
if (OSUtils.isMac()) {
|
||||
libName = "libArthasJniLibrary-x64.dylib";
|
||||
}
|
||||
if (OSUtils.isLinux()) {
|
||||
libName = "libArthasJniLibrary-x64.so";
|
||||
if (OSUtils.isArm32()) {
|
||||
libName = "libArthasJniLibrary-arm.so";
|
||||
} else if (OSUtils.isArm64()) {
|
||||
libName = "libArthasJniLibrary-aarch64.so";
|
||||
}
|
||||
}
|
||||
if (OSUtils.isWindows()) {
|
||||
libName = "libArthasJniLibrary-x64.dll";
|
||||
}
|
||||
}
|
||||
|
||||
public static String detectLibName() {
|
||||
return libName;
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import com.taobao.arthas.core.command.monitor200.StackCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.ThreadCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.TimeTunnelCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.TraceCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.VmToolCommand;
|
||||
import com.taobao.arthas.core.command.monitor200.WatchCommand;
|
||||
import com.taobao.arthas.core.shell.command.Command;
|
||||
import com.taobao.arthas.core.shell.command.CommandResolver;
|
||||
@@ -113,6 +114,7 @@ public class BuiltinCommandPack implements CommandResolver {
|
||||
commands.add(Command.create(GrepCommand.class));
|
||||
commands.add(Command.create(TeeCommand.class));
|
||||
commands.add(Command.create(ProfilerCommand.class));
|
||||
commands.add(Command.create(VmToolCommand.class));
|
||||
commands.add(Command.create(ShutdownCommand.class));
|
||||
commands.add(Command.create(StopCommand.class));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
package com.taobao.arthas.core.command.monitor200;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.security.CodeSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.common.VmToolUtils;
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.command.express.Express;
|
||||
import com.taobao.arthas.core.command.express.ExpressException;
|
||||
import com.taobao.arthas.core.command.express.ExpressFactory;
|
||||
import com.taobao.arthas.core.command.model.ClassLoaderVO;
|
||||
import com.taobao.arthas.core.command.model.SearchClassModel;
|
||||
import com.taobao.arthas.core.shell.cli.Completion;
|
||||
import com.taobao.arthas.core.shell.cli.CompletionUtils;
|
||||
import com.taobao.arthas.core.shell.cli.OptionCompleteHandler;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.util.ClassLoaderUtils;
|
||||
import com.taobao.arthas.core.util.ClassUtils;
|
||||
import com.taobao.arthas.core.util.SearchUtils;
|
||||
import com.taobao.arthas.core.view.ObjectView;
|
||||
import com.taobao.middleware.cli.annotations.DefaultValue;
|
||||
import com.taobao.middleware.cli.annotations.Description;
|
||||
import com.taobao.middleware.cli.annotations.Name;
|
||||
import com.taobao.middleware.cli.annotations.Option;
|
||||
import com.taobao.middleware.cli.annotations.Summary;
|
||||
|
||||
import arthas.VmTool;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hengyunabc 2021-04-27
|
||||
*
|
||||
*/
|
||||
//@formatter:off
|
||||
@Name("vmtool")
|
||||
@Summary("jvm tool")
|
||||
@Description(Constants.EXAMPLE
|
||||
+ " vmtool --action getInstances --className demo.MathGame\n"
|
||||
+ " vmtool --action getInstances --className demo.MathGame --express 'instances.size()'\n"
|
||||
+ Constants.WIKI + Constants.WIKI_HOME + "vmtool")
|
||||
//@formatter:on
|
||||
public class VmToolCommand extends AnnotatedCommand {
|
||||
private static final Logger logger = LoggerFactory.getLogger(VmToolCommand.class);
|
||||
|
||||
private VmToolAction action;
|
||||
private String className;
|
||||
private String express;
|
||||
|
||||
private String hashCode = null;
|
||||
private String classLoaderClass;
|
||||
/**
|
||||
* default value 2
|
||||
*/
|
||||
private int expand;
|
||||
|
||||
private static String libPath;
|
||||
private static VmTool vmTool = null;
|
||||
|
||||
static {
|
||||
String libName = VmToolUtils.detectLibName();
|
||||
if (libName != null) {
|
||||
CodeSource codeSource = VmToolCommand.class.getProtectionDomain().getCodeSource();
|
||||
if (codeSource != null) {
|
||||
try {
|
||||
File bootJarPath = new File(codeSource.getLocation().toURI().getSchemeSpecificPart());
|
||||
File soFile = new File(bootJarPath.getParentFile(), "lib" + File.separator + libName);
|
||||
if (soFile.exists()) {
|
||||
libPath = soFile.getAbsolutePath();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.error("can not find VmTool so", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Option(shortName = "a", longName = "action", required = true)
|
||||
@Description("Action to execute")
|
||||
public void setAction(VmToolAction action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Option(longName = "className")
|
||||
@Description("The class name")
|
||||
public void setClassName(String className) {
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
@Option(shortName = "x", longName = "expand")
|
||||
@Description("Expand level of object (2 by default)")
|
||||
@DefaultValue("2")
|
||||
public void setExpand(int expand) {
|
||||
this.expand = expand;
|
||||
}
|
||||
|
||||
@Option(shortName = "c", longName = "classloader")
|
||||
@Description("The hash code of the special class's classLoader")
|
||||
public void setHashCode(String hashCode) {
|
||||
this.hashCode = hashCode;
|
||||
}
|
||||
|
||||
@Option(longName = "classLoaderClass")
|
||||
@Description("The class name of the special class's classLoader.")
|
||||
public void setClassLoaderClass(String classLoaderClass) {
|
||||
this.classLoaderClass = classLoaderClass;
|
||||
}
|
||||
|
||||
@Option(longName = "express", required = false)
|
||||
@Description("The ognl expression, default valueis `instances`.")
|
||||
public void setExpress(String express) {
|
||||
this.express = express;
|
||||
}
|
||||
|
||||
public enum VmToolAction {
|
||||
getInstances, load
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(final CommandProcess process) {
|
||||
try {
|
||||
Instrumentation inst = process.session().getInstrumentation();
|
||||
|
||||
if (VmToolAction.getInstances.equals(action)) {
|
||||
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
|
||||
if (hashCode == null && classLoaderClass != null) {
|
||||
List<ClassLoader> matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst,
|
||||
classLoaderClass);
|
||||
if (matchedClassLoaders.size() == 1) {
|
||||
classLoader = matchedClassLoaders.get(0);
|
||||
hashCode = Integer.toHexString(matchedClassLoaders.get(0).hashCode());
|
||||
} else if (matchedClassLoaders.size() > 1) {
|
||||
Collection<ClassLoaderVO> classLoaderVOList = ClassUtils
|
||||
.createClassLoaderVOList(matchedClassLoaders);
|
||||
SearchClassModel searchclassModel = new SearchClassModel().setClassLoaderClass(classLoaderClass)
|
||||
.setMatchedClassLoaders(classLoaderVOList);
|
||||
process.appendResult(searchclassModel);
|
||||
process.end(-1,
|
||||
"Found more than one classloader by class name, please specify classloader with '-c <classloader hash>'");
|
||||
return;
|
||||
} else {
|
||||
process.end(-1, "Can not find classloader by class name: " + classLoaderClass + ".");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
List<Class<?>> matchedClasses = new ArrayList<Class<?>>(
|
||||
SearchUtils.searchClass(inst, className, false, hashCode));
|
||||
int matchedClassSize = matchedClasses.size();
|
||||
if (matchedClassSize == 0) {
|
||||
process.end(-1, "Can not find class by class name: " + className + ".");
|
||||
return;
|
||||
} else if (matchedClassSize > 1) {
|
||||
process.end(-1, "Found more than one class: " + matchedClasses + ".");
|
||||
return;
|
||||
} else {
|
||||
ArrayList<?> instances = vmToolInstance().getInstances(matchedClasses.get(0));
|
||||
Object value = instances;
|
||||
if (express != null) {
|
||||
Express unpooledExpress = ExpressFactory.unpooledExpress(classLoader);
|
||||
try {
|
||||
value = unpooledExpress.bind(new InstancesWrapper(instances)).get(express);
|
||||
} catch (ExpressException e) {
|
||||
logger.warn("ognl: failed execute express: " + express, e);
|
||||
process.end(-1, "Failed to execute ognl, exception message: " + e.getMessage()
|
||||
+ ", please check $HOME/logs/arthas/arthas.log for more details. ");
|
||||
}
|
||||
}
|
||||
|
||||
process.write(new ObjectView(value, this.expand).draw());
|
||||
process.end();
|
||||
}
|
||||
}
|
||||
|
||||
process.end();
|
||||
} catch (Throwable e) {
|
||||
logger.error("vmtool error", e);
|
||||
process.end(1, "vmtool error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
static class InstancesWrapper {
|
||||
Object instances;
|
||||
|
||||
public InstancesWrapper(Object instances) {
|
||||
this.instances = instances;
|
||||
}
|
||||
|
||||
public Object getInstances() {
|
||||
return instances;
|
||||
}
|
||||
|
||||
public void setInstances(Object instances) {
|
||||
this.instances = instances;
|
||||
}
|
||||
}
|
||||
|
||||
private VmTool vmToolInstance() {
|
||||
if (vmTool != null) {
|
||||
return vmTool;
|
||||
} else {
|
||||
vmTool = VmTool.getInstance(libPath);
|
||||
}
|
||||
return vmTool;
|
||||
}
|
||||
|
||||
private Set<String> actions() {
|
||||
Set<String> values = new HashSet<String>();
|
||||
for (VmToolAction action : VmToolAction.values()) {
|
||||
values.add(action.toString());
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void complete(Completion completion) {
|
||||
List<OptionCompleteHandler> handlers = new ArrayList<OptionCompleteHandler>();
|
||||
|
||||
handlers.add(new OptionCompleteHandler() {
|
||||
|
||||
@Override
|
||||
public boolean matchName(String token) {
|
||||
return "-a".equals(token) || "--action".equals(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(Completion completion) {
|
||||
return CompletionUtils.complete(completion, actions());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
handlers.add(new OptionCompleteHandler() {
|
||||
@Override
|
||||
public boolean matchName(String token) {
|
||||
return "--className".equals(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(Completion completion) {
|
||||
return CompletionUtils.completeClassName(completion);
|
||||
}
|
||||
});
|
||||
|
||||
if (CompletionUtils.completeOptions(completion, handlers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.complete(completion);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -181,7 +181,7 @@ public class ClassUtils {
|
||||
return list.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static List<ClassVO> createClassVOList(Set<Class<?>> matchedClasses) {
|
||||
public static List<ClassVO> createClassVOList(Collection<Class<?>> matchedClasses) {
|
||||
List<ClassVO> classVOs = new ArrayList<ClassVO>(matchedClasses.size());
|
||||
for (Class<?> aClass : matchedClasses) {
|
||||
ClassVO classVO = createSimpleClassInfo(aClass);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -63,5 +63,8 @@
|
||||
<fileSet>
|
||||
<directory>../async-profiler</directory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>../lib</directory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
|
||||
@@ -57,8 +57,8 @@
|
||||
|
||||
<modules>
|
||||
<module>math-game</module>
|
||||
<module>spy</module>
|
||||
<module>common</module>
|
||||
<module>spy</module>
|
||||
<module>arthas-vmtool</module>
|
||||
<module>tunnel-common</module>
|
||||
<module>tunnel-client</module>
|
||||
|
||||
+10
@@ -10,6 +10,16 @@
|
||||
<artifactId>arthas-spy</artifactId>
|
||||
<name>arthas-spy</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.taobao.arthas</groupId>
|
||||
<artifactId>arthas-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>arthas-spy</finalName>
|
||||
<plugins>
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package arthas;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @author ZhangZiCheng 2021-02-12
|
||||
* @author hengyunabc 2021-04-26
|
||||
* @since 3.5.1
|
||||
*/
|
||||
public class VmTool implements VmToolMXBean {
|
||||
|
||||
/**
|
||||
* 不要修改jni-lib的名称
|
||||
*/
|
||||
public final static String JNI_LIBRARY_NAME = "ArthasJniLibrary";
|
||||
|
||||
private static VmTool instance;
|
||||
|
||||
private VmTool() {
|
||||
}
|
||||
|
||||
public static VmTool getInstance() {
|
||||
return getInstance(null);
|
||||
}
|
||||
|
||||
public static synchronized VmTool getInstance(String libPath) {
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
if (libPath == null) {
|
||||
System.loadLibrary(JNI_LIBRARY_NAME);
|
||||
} else {
|
||||
System.load(libPath);
|
||||
}
|
||||
|
||||
instance = new VmTool();
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测jni-lib是否正常,如果正常,应该输出OK
|
||||
*/
|
||||
private static native String check0();
|
||||
|
||||
/**
|
||||
* 获取某个class在jvm中当前所有存活实例
|
||||
*/
|
||||
private static native <T> ArrayList<T> getInstances0(Class<T> klass);
|
||||
|
||||
/**
|
||||
* 统计某个class在jvm中当前所有存活实例的总占用内存,单位:Byte
|
||||
*/
|
||||
private static native long sumInstanceSize0(Class<?> klass);
|
||||
|
||||
/**
|
||||
* 获取某个实例的占用内存,单位:Byte
|
||||
*/
|
||||
private static native long getInstanceSize0(Object instance);
|
||||
|
||||
/**
|
||||
* 统计某个class在jvm中当前所有存活实例的总个数
|
||||
*/
|
||||
private static native long countInstances0(Class<?> klass);
|
||||
|
||||
/**
|
||||
* 获取所有已加载的类
|
||||
*/
|
||||
private static native ArrayList<Class<?>> getAllLoadedClasses0();
|
||||
|
||||
/**
|
||||
* 包括小类型(如int)
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
public static ArrayList<Class> getAllClasses() {
|
||||
return getInstances0(Class.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String check() {
|
||||
return check0();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ArrayList<T> getInstances(Class<T> klass) {
|
||||
return getInstances0(klass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sumInstanceSize(Class<?> klass) {
|
||||
return sumInstanceSize0(klass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getInstanceSize(Object instance) {
|
||||
return getInstanceSize0(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countInstances(Class<?> klass) {
|
||||
return countInstances0(klass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Class<?>> getAllLoadedClasses() {
|
||||
return getAllLoadedClasses0();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package arthas;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* VmTool interface for JMX server. How to register VmTool MBean:
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* ManagementFactory.getPlatformMBeanServer().registerMBean(
|
||||
* VmTool.getInstance(),
|
||||
* new ObjectName("arthas:type=VmTool")
|
||||
* );
|
||||
* }
|
||||
* </pre>
|
||||
* @author hengyunabc 2021-04-26
|
||||
*/
|
||||
public interface VmToolMXBean {
|
||||
/**
|
||||
* 检测jni-lib是否正常,如果正常,应该输出OK
|
||||
*/
|
||||
public String check();
|
||||
|
||||
/**
|
||||
* 获取某个class在jvm中当前所有存活实例
|
||||
*/
|
||||
public <T> ArrayList<T> getInstances(Class<T> klass);
|
||||
|
||||
/**
|
||||
* 统计某个class在jvm中当前所有存活实例的总占用内存,单位:Byte
|
||||
*/
|
||||
public long sumInstanceSize(Class<?> klass);
|
||||
|
||||
/**
|
||||
* 获取某个实例的占用内存,单位:Byte
|
||||
*/
|
||||
public long getInstanceSize(Object instance);
|
||||
|
||||
/**
|
||||
* 统计某个class在jvm中当前所有存活实例的总个数
|
||||
*/
|
||||
public long countInstances(Class<?> klass);
|
||||
|
||||
/**
|
||||
* 获取所有已加载的类
|
||||
*/
|
||||
public ArrayList<Class<?>> getAllLoadedClasses();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* <pre>
|
||||
* copy from arthas-vmtool/src/main/java 。
|
||||
* 因为动态链接库只能被加载一次,只能使用一份代码。放在spy jar里保证只有一份。
|
||||
* TODO 当arthas本身版本升级时,已append 到bootstrap classloader的spy jar不能升级,VmTool的接口可以会调用失败。
|
||||
* </pre>
|
||||
*/
|
||||
package arthas;
|
||||
Reference in New Issue
Block a user