Commit 7c094a26 authored by liang.tang's avatar liang.tang
Browse files

arthas-master

parents
Pipeline #220 failed with stages
in 0 seconds
package com.taobao.arthas.core.command.klass100;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.express.ExpressException;
import com.taobao.arthas.core.command.express.ExpressFactory;
import com.taobao.arthas.core.command.model.ClassVO;
import com.taobao.arthas.core.command.model.ClassLoaderVO;
import com.taobao.arthas.core.command.model.GetStaticModel;
import com.taobao.arthas.core.command.model.MessageModel;
import com.taobao.arthas.core.command.model.RowAffectModel;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.shell.command.ExitStatus;
import com.taobao.arthas.core.util.ClassUtils;
import com.taobao.arthas.core.util.ClassLoaderUtils;
import com.taobao.arthas.core.util.CommandUtils;
import com.taobao.arthas.core.util.SearchUtils;
import com.taobao.arthas.core.util.StringUtils;
import com.taobao.arthas.core.util.affect.RowAffect;
import com.taobao.arthas.core.util.matcher.Matcher;
import com.taobao.arthas.core.util.matcher.RegexMatcher;
import com.taobao.arthas.core.util.matcher.WildcardMatcher;
import com.taobao.middleware.cli.annotations.Argument;
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 java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Set;
import java.util.Collection;
/**
* @author diecui1202 on 2017/9/27.
*/
@Name("getstatic")
@Summary("Show the static field of a class")
@Description(Constants.EXAMPLE +
" getstatic demo.MathGame random\n" +
" getstatic -c 39eb305e org.apache.log4j.LogManager DEFAULT_CONFIGURATION_FILE\n" +
Constants.WIKI + Constants.WIKI_HOME + "getstatic")
public class GetStaticCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(GetStaticCommand.class);
private String classPattern;
private String fieldPattern;
private String express;
private String hashCode = null;
private String classLoaderClass;
private boolean isRegEx = false;
private int expand = 1;
@Argument(argName = "class-pattern", index = 0)
@Description("Class name pattern, use either '.' or '/' as separator")
public void setClassPattern(String classPattern) {
this.classPattern = classPattern;
}
@Argument(argName = "field-pattern", index = 1)
@Description("Field name pattern")
public void setFieldPattern(String fieldPattern) {
this.fieldPattern = fieldPattern;
}
@Argument(argName = "express", index = 2, required = false)
@Description("the content you want to watch, written by ognl")
public void setExpress(String express) {
this.express = express;
}
@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(shortName = "E", longName = "regex", flag = true)
@Description("Enable regular expression to match (wildcard matching by default)")
public void setRegEx(boolean regEx) {
isRegEx = regEx;
}
@Option(shortName = "x", longName = "expand")
@Description("Expand level of object (1 by default)")
public void setExpand(Integer expand) {
this.expand = expand;
}
@Override
public void process(CommandProcess process) {
RowAffect affect = new RowAffect();
Instrumentation inst = process.session().getInstrumentation();
if (hashCode == null && classLoaderClass != null) {
List<ClassLoader> matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst, classLoaderClass);
if (matchedClassLoaders.size() == 1) {
hashCode = Integer.toHexString(matchedClassLoaders.get(0).hashCode());
} else if (matchedClassLoaders.size() > 1) {
Collection<ClassLoaderVO> classLoaderVOList = ClassUtils.createClassLoaderVOList(matchedClassLoaders);
GetStaticModel getStaticModel = new GetStaticModel()
.setClassLoaderClass(classLoaderClass)
.setMatchedClassLoaders(classLoaderVOList);
process.appendResult(getStaticModel);
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;
}
}
Set<Class<?>> matchedClasses = SearchUtils.searchClassOnly(inst, classPattern, isRegEx, hashCode);
try {
if (matchedClasses == null || matchedClasses.isEmpty()) {
process.end(-1, "No class found for: " + classPattern);
return;
}
ExitStatus status = null;
if (matchedClasses.size() > 1) {
status = processMatches(process, matchedClasses);
} else {
status = processExactMatch(process, affect, inst, matchedClasses);
}
process.appendResult(new RowAffectModel(affect));
CommandUtils.end(process, status);
} catch (Throwable e){
logger.error("processing error", e);
process.appendResult(new RowAffectModel(affect));
process.end(-1, "processing error");
}
}
private ExitStatus processExactMatch(CommandProcess process, RowAffect affect, Instrumentation inst,
Set<Class<?>> matchedClasses) {
Matcher<String> fieldNameMatcher = fieldNameMatcher();
Class<?> clazz = matchedClasses.iterator().next();
boolean found = false;
for (Field field : clazz.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers()) || !fieldNameMatcher.matching(field.getName())) {
continue;
}
if (!field.isAccessible()) {
field.setAccessible(true);
}
try {
Object value = field.get(null);
if (!StringUtils.isEmpty(express)) {
value = ExpressFactory.threadLocalExpress(value).get(express);
}
process.appendResult(new GetStaticModel(field.getName(), value, expand));
affect.rCnt(1);
} catch (IllegalAccessException e) {
logger.warn("getstatic: failed to get static value, class: {}, field: {} ", clazz, field.getName(), e);
process.appendResult(new MessageModel("Failed to get static, exception message: " + e.getMessage()
+ ", please check $HOME/logs/arthas/arthas.log for more details. "));
} catch (ExpressException e) {
logger.warn("getstatic: failed to get express value, class: {}, field: {}, express: {}", clazz, field.getName(), express, e);
process.appendResult(new MessageModel("Failed to get static, exception message: " + e.getMessage()
+ ", please check $HOME/logs/arthas/arthas.log for more details. "));
} finally {
found = true;
}
}
if (!found) {
return ExitStatus.failure(-1, "getstatic: no matched static field was found");
} else {
return ExitStatus.success();
}
}
private ExitStatus processMatches(CommandProcess process, Set<Class<?>> matchedClasses) {
// Element usage = new LabelElement("getstatic -c <hashcode> " + classPattern + " " + fieldPattern).style(
// Decoration.bold.fg(Color.blue));
// process.write("\n Found more than one class for: " + classPattern + ", Please use " + RenderUtil.render(usage, process.width()));
//TODO support message style
String usage = "getstatic -c <hashcode> " + classPattern + " " + fieldPattern;
process.appendResult(new MessageModel("Found more than one class for: " + classPattern + ", Please use: "+usage));
List<ClassVO> matchedClassVOs = ClassUtils.createClassVOList(matchedClasses);
process.appendResult(new GetStaticModel(matchedClassVOs));
return ExitStatus.failure(-1, "Found more than one class for: " + classPattern + ", Please use: "+usage);
}
private Matcher<String> fieldNameMatcher() {
return isRegEx ? new RegexMatcher(fieldPattern) : new WildcardMatcher(fieldPattern);
}
}
package com.taobao.arthas.core.command.klass100;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.common.Pair;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.model.ClassVO;
import com.taobao.arthas.core.command.model.ClassLoaderVO;
import com.taobao.arthas.core.command.model.JadModel;
import com.taobao.arthas.core.command.model.MessageModel;
import com.taobao.arthas.core.command.model.RowAffectModel;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.cli.CompletionUtils;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.shell.command.ExitStatus;
import com.taobao.arthas.core.util.ClassUtils;
import com.taobao.arthas.core.util.ClassLoaderUtils;
import com.taobao.arthas.core.util.CommandUtils;
import com.taobao.arthas.core.util.Decompiler;
import com.taobao.arthas.core.util.InstrumentationUtils;
import com.taobao.arthas.core.util.SearchUtils;
import com.taobao.arthas.core.util.affect.RowAffect;
import com.taobao.middleware.cli.annotations.Argument;
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 java.io.File;
import java.lang.instrument.Instrumentation;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.Collection;
import java.util.regex.Pattern;
/**
* @author diecui1202 on 15/11/24.
* @author hengyunabc 2018-11-16
*/
@Name("jad")
@Summary("Decompile class")
@Description(Constants.EXAMPLE +
" jad java.lang.String\n" +
" jad java.lang.String toString\n" +
" jad --source-only java.lang.String\n" +
" jad -c 39eb305e org/apache/log4j/Logger\n" +
" jad -c 39eb305e -E org\\\\.apache\\\\.*\\\\.StringUtils\n" +
Constants.WIKI + Constants.WIKI_HOME + "jad")
public class JadCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(JadCommand.class);
private static Pattern pattern = Pattern.compile("(?m)^/\\*\\s*\\*/\\s*$" + System.getProperty("line.separator"));
private String classPattern;
private String methodName;
private String code = null;
private String classLoaderClass;
private boolean isRegEx = false;
private boolean hideUnicode = false;
private boolean lineNumber;
/**
* jad output source code only
*/
private boolean sourceOnly = false;
@Argument(argName = "class-pattern", index = 0)
@Description("Class name pattern, use either '.' or '/' as separator")
public void setClassPattern(String classPattern) {
this.classPattern = classPattern;
}
@Argument(argName = "method-name", index = 1, required = false)
@Description("method name pattern, decompile a specific method instead of the whole class")
public void setMethodName(String methodName) {
this.methodName = methodName;
}
@Option(shortName = "c", longName = "code")
@Description("The hash code of the special class's classLoader")
public void setCode(String code) {
this.code = code;
}
@Option(longName = "classLoaderClass")
@Description("The class name of the special class's classLoader.")
public void setClassLoaderClass(String classLoaderClass) {
this.classLoaderClass = classLoaderClass;
}
@Option(shortName = "E", longName = "regex", flag = true)
@Description("Enable regular expression to match (wildcard matching by default)")
public void setRegEx(boolean regEx) {
isRegEx = regEx;
}
@Option(longName = "hideUnicode", flag = true)
@Description("hide unicode, default value false")
public void setHideUnicode(boolean hideUnicode) {
this.hideUnicode = hideUnicode;
}
@Option(longName = "source-only", flag = true)
@Description("Output source code only")
public void setSourceOnly(boolean sourceOnly) {
this.sourceOnly = sourceOnly;
}
@Option(longName = "lineNumber")
@DefaultValue("true")
@Description("Output source code contins line number, default value true")
public void setLineNumber(boolean lineNumber) {
this.lineNumber = lineNumber;
}
@Override
public void process(CommandProcess process) {
RowAffect affect = new RowAffect();
Instrumentation inst = process.session().getInstrumentation();
if (code == null && classLoaderClass != null) {
List<ClassLoader> matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst, classLoaderClass);
if (matchedClassLoaders.size() == 1) {
code = Integer.toHexString(matchedClassLoaders.get(0).hashCode());
} else if (matchedClassLoaders.size() > 1) {
Collection<ClassLoaderVO> classLoaderVOList = ClassUtils.createClassLoaderVOList(matchedClassLoaders);
JadModel jadModel = new JadModel()
.setClassLoaderClass(classLoaderClass)
.setMatchedClassLoaders(classLoaderVOList);
process.appendResult(jadModel);
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;
}
}
Set<Class<?>> matchedClasses = SearchUtils.searchClassOnly(inst, classPattern, isRegEx, code);
try {
ExitStatus status = null;
if (matchedClasses == null || matchedClasses.isEmpty()) {
status = processNoMatch(process);
} else if (matchedClasses.size() > 1) {
status = processMatches(process, matchedClasses);
} else { // matchedClasses size is 1
// find inner classes.
Set<Class<?>> withInnerClasses = SearchUtils.searchClassOnly(inst, matchedClasses.iterator().next().getName() + "$*", false, code);
if(withInnerClasses.isEmpty()) {
withInnerClasses = matchedClasses;
}
status = processExactMatch(process, affect, inst, matchedClasses, withInnerClasses);
}
if (!this.sourceOnly) {
process.appendResult(new RowAffectModel(affect));
}
CommandUtils.end(process, status);
} catch (Throwable e){
logger.error("processing error", e);
process.end(-1, "processing error");
}
}
private ExitStatus processExactMatch(CommandProcess process, RowAffect affect, Instrumentation inst, Set<Class<?>> matchedClasses, Set<Class<?>> withInnerClasses) {
Class<?> c = matchedClasses.iterator().next();
Set<Class<?>> allClasses = new HashSet<Class<?>>(withInnerClasses);
allClasses.add(c);
try {
ClassDumpTransformer transformer = new ClassDumpTransformer(allClasses);
InstrumentationUtils.retransformClasses(inst, transformer, allClasses);
Map<Class<?>, File> classFiles = transformer.getDumpResult();
File classFile = classFiles.get(c);
Pair<String,NavigableMap<Integer,Integer>> decompileResult = Decompiler.decompileWithMappings(classFile.getAbsolutePath(), methodName, hideUnicode, lineNumber);
String source = decompileResult.getFirst();
if (source != null) {
source = pattern.matcher(source).replaceAll("");
} else {
source = "unknown";
}
JadModel jadModel = new JadModel();
jadModel.setSource(source);
jadModel.setMappings(decompileResult.getSecond());
if (!this.sourceOnly) {
jadModel.setClassInfo(ClassUtils.createSimpleClassInfo(c));
jadModel.setLocation(ClassUtils.getCodeSource(c.getProtectionDomain().getCodeSource()));
}
process.appendResult(jadModel);
affect.rCnt(classFiles.keySet().size());
return ExitStatus.success();
} catch (Throwable t) {
logger.error("jad: fail to decompile class: " + c.getName(), t);
return ExitStatus.failure(-1, "jad: fail to decompile class: " + c.getName() + ", please check $HOME/logs/arthas/arthas.log for more details.");
}
}
private ExitStatus processMatches(CommandProcess process, Set<Class<?>> matchedClasses) {
String usage = "jad -c <hashcode> " + classPattern;
String msg = " Found more than one class for: " + classPattern + ", Please use " + usage;
process.appendResult(new MessageModel(msg));
List<ClassVO> classVOs = ClassUtils.createClassVOList(matchedClasses);
JadModel jadModel = new JadModel();
jadModel.setMatchedClasses(classVOs);
process.appendResult(jadModel);
return ExitStatus.failure(-1, msg);
}
private ExitStatus processNoMatch(CommandProcess process) {
return ExitStatus.failure(-1, "No class found for: " + classPattern);
}
@Override
public void complete(Completion completion) {
int argumentIndex = CompletionUtils.detectArgumentIndex(completion);
if (argumentIndex == 1) {
if (!CompletionUtils.completeClassName(completion)) {
super.complete(completion);
}
return;
} else if (argumentIndex == 2) {
if (!CompletionUtils.completeMethodName(completion)) {
super.complete(completion);
}
return;
}
super.complete(completion);
}
}
package com.taobao.arthas.core.command.klass100;
import java.io.File;
import java.lang.instrument.Instrumentation;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Collection;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.compiler.DynamicCompiler;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.model.MemoryCompilerModel;
import com.taobao.arthas.core.command.model.RowAffectModel;
import com.taobao.arthas.core.command.model.ClassLoaderVO;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.cli.CompletionUtils;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.util.FileUtils;
import com.taobao.arthas.core.util.ClassUtils;
import com.taobao.arthas.core.util.ClassLoaderUtils;
import com.taobao.arthas.core.util.affect.RowAffect;
import com.taobao.middleware.cli.annotations.Argument;
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;
/**
*
* @author hengyunabc 2019-02-05
*
*/
@Name("mc")
@Summary("Memory compiler, compiles java files into bytecode and class files in memory.")
@Description(Constants.EXAMPLE + " mc /tmp/Test.java\n" + " mc -c 327a647b /tmp/Test.java\n"
+ " mc -d /tmp/output /tmp/ClassA.java /tmp/ClassB.java\n" + Constants.WIKI + Constants.WIKI_HOME
+ "mc")
public class MemoryCompilerCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(MemoryCompilerCommand.class);
private String directory;
private String hashCode;
private String classLoaderClass;
private String encoding;
private List<String> sourcefiles;
@Argument(argName = "sourcefiles", index = 0)
@Description("source files")
public void setClassPattern(List<String> sourcefiles) {
this.sourcefiles = sourcefiles;
}
@Option(shortName = "c", longName = "classloader")
@Description("The hash code of the special 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 = "encoding")
@Description("Source file encoding")
public void setEncoding(String encoding) {
this.encoding = encoding;
}
@Option(shortName = "d", longName = "directory")
@Description("Sets the destination directory for class files")
public void setDirectory(String directory) {
this.directory = directory;
}
@Override
public void process(final CommandProcess process) {
RowAffect affect = new RowAffect();
try {
Instrumentation inst = process.session().getInstrumentation();
if (hashCode == null && classLoaderClass != null) {
List<ClassLoader> matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst, classLoaderClass);
if (matchedClassLoaders.size() == 1) {
hashCode = Integer.toHexString(matchedClassLoaders.get(0).hashCode());
} else if (matchedClassLoaders.size() > 1) {
Collection<ClassLoaderVO> classLoaderVOList = ClassUtils.createClassLoaderVOList(matchedClassLoaders);
MemoryCompilerModel memoryCompilerModel = new MemoryCompilerModel()
.setClassLoaderClass(classLoaderClass)
.setMatchedClassLoaders(classLoaderVOList);
process.appendResult(memoryCompilerModel);
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;
}
}
ClassLoader classloader = null;
if (hashCode == null) {
classloader = ClassLoader.getSystemClassLoader();
} else {
classloader = ClassLoaderUtils.getClassLoader(inst, hashCode);
if (classloader == null) {
process.end(-1, "Can not find classloader with hashCode: " + hashCode + ".");
return;
}
}
DynamicCompiler dynamicCompiler = new DynamicCompiler(classloader);
Charset charset = Charset.defaultCharset();
if (encoding != null) {
charset = Charset.forName(encoding);
}
for (String sourceFile : sourcefiles) {
String sourceCode = FileUtils.readFileToString(new File(sourceFile), charset);
String name = new File(sourceFile).getName();
if (name.endsWith(".java")) {
name = name.substring(0, name.length() - ".java".length());
}
dynamicCompiler.addSource(name, sourceCode);
}
Map<String, byte[]> byteCodes = dynamicCompiler.buildByteCodes();
File outputDir = null;
if (this.directory != null) {
outputDir = new File(this.directory);
} else {
outputDir = new File("").getAbsoluteFile();
}
List<String> files = new ArrayList<String>();
for (Entry<String, byte[]> entry : byteCodes.entrySet()) {
File byteCodeFile = new File(outputDir, entry.getKey().replace('.', '/') + ".class");
FileUtils.writeByteArrayToFile(byteCodeFile, entry.getValue());
files.add(byteCodeFile.getAbsolutePath());
affect.rCnt(1);
}
process.appendResult(new MemoryCompilerModel(files));
process.appendResult(new RowAffectModel(affect));
process.end();
} catch (Throwable e) {
logger.warn("Memory compiler error", e);
process.end(-1, "Memory compiler error, exception message: " + e.getMessage()
+ ", please check $HOME/logs/arthas/arthas.log for more details.");
}
}
@Override
public void complete(Completion completion) {
if (!CompletionUtils.completeFilePath(completion)) {
super.complete(completion);
}
}
}
package com.taobao.arthas.core.command.klass100;
import java.lang.instrument.Instrumentation;
import java.util.Collection;
import java.util.List;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
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.ObjectVO;
import com.taobao.arthas.core.command.model.OgnlModel;
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.middleware.cli.annotations.Argument;
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;
/**
*
* @author hengyunabc 2018-10-18
*
*/
@Name("ognl")
@Summary("Execute ognl expression.")
@Description(Constants.EXAMPLE
+ " ognl '@java.lang.System@out.println(\"hello \\u4e2d\\u6587\")' \n"
+ " ognl -x 2 '@Singleton@getInstance()' \n"
+ " ognl '@Demo@staticFiled' \n"
+ " ognl '#value1=@System@getProperty(\"java.home\"), #value2=@System@getProperty(\"java.runtime.name\"), {#value1, #value2}'\n"
+ " ognl -c 5d113a51 '@com.taobao.arthas.core.GlobalOptions@isDump' \n"
+ Constants.WIKI + Constants.WIKI_HOME + "ognl\n"
+ " https://commons.apache.org/proper/commons-ognl/language-guide.html")
public class OgnlCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(OgnlCommand.class);
private String express;
private String hashCode;
private String classLoaderClass;
private int expand = 1;
@Argument(argName = "express", index = 0, required = true)
@Description("The ognl expression.")
public void setExpress(String express) {
this.express = express;
}
@Option(shortName = "c", longName = "classLoader")
@Description("The hash code of the special class's classLoader, default classLoader is SystemClassLoader.")
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(shortName = "x", longName = "expand")
@Description("Expand level of object (1 by default).")
public void setExpand(Integer expand) {
this.expand = expand;
}
@Override
public void process(CommandProcess process) {
Instrumentation inst = process.session().getInstrumentation();
ClassLoader classLoader = null;
if (hashCode != null) {
classLoader = ClassLoaderUtils.getClassLoader(inst, hashCode);
if (classLoader == null) {
process.end(-1, "Can not find classloader with hashCode: " + hashCode + ".");
return;
}
} else if (classLoaderClass != null) {
List<ClassLoader> matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst, classLoaderClass);
if (matchedClassLoaders.size() == 1) {
classLoader = matchedClassLoaders.get(0);
} else if (matchedClassLoaders.size() > 1) {
Collection<ClassLoaderVO> classLoaderVOList = ClassUtils.createClassLoaderVOList(matchedClassLoaders);
OgnlModel ognlModel = new OgnlModel()
.setClassLoaderClass(classLoaderClass)
.setMatchedClassLoaders(classLoaderVOList);
process.appendResult(ognlModel);
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;
}
} else {
classLoader = ClassLoader.getSystemClassLoader();
}
Express unpooledExpress = ExpressFactory.unpooledExpress(classLoader);
try {
Object value = unpooledExpress.get(express);
OgnlModel ognlModel = new OgnlModel()
.setValue(new ObjectVO(value, expand));
process.appendResult(ognlModel);
process.end();
} 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. ");
}
}
}
package com.taobao.arthas.core.command.klass100;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import com.alibaba.deps.org.objectweb.asm.ClassReader;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.model.RedefineModel;
import com.taobao.arthas.core.command.model.ClassLoaderVO;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.cli.CompletionUtils;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.util.ClassUtils;
import com.taobao.arthas.core.util.ClassLoaderUtils;
import com.taobao.middleware.cli.annotations.Argument;
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;
/**
* Redefine Classes.
*
* @author hengyunabc 2018-07-13
* @see java.lang.instrument.Instrumentation#redefineClasses(ClassDefinition...)
*/
@Name("redefine")
@Summary("Redefine classes. @see Instrumentation#redefineClasses(ClassDefinition...)")
@Description(Constants.EXAMPLE +
" redefine /tmp/Test.class\n" +
" redefine -c 327a647b /tmp/Test.class /tmp/Test\\$Inner.class \n" +
" redefine --classLoaderClass 'sun.misc.Launcher$AppClassLoader' /tmp/Test.class \n" +
Constants.WIKI + Constants.WIKI_HOME + "redefine")
public class RedefineCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(RedefineCommand.class);
private static final int MAX_FILE_SIZE = 10 * 1024 * 1024;
private String hashCode;
private String classLoaderClass;
private List<String> paths;
@Option(shortName = "c", longName = "classloader")
@Description("classLoader hashcode")
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;
}
@Argument(argName = "classfilePaths", index = 0)
@Description(".class file paths")
public void setPaths(List<String> paths) {
this.paths = paths;
}
@Override
public void process(CommandProcess process) {
RedefineModel redefineModel = new RedefineModel();
Instrumentation inst = process.session().getInstrumentation();
for (String path : paths) {
File file = new File(path);
if (!file.exists()) {
process.end(-1, "file does not exist, path:" + path);
return;
}
if (!file.isFile()) {
process.end(-1, "not a normal file, path:" + path);
return;
}
if (file.length() >= MAX_FILE_SIZE) {
process.end(-1, "file size: " + file.length() + " >= " + MAX_FILE_SIZE + ", path: " + path);
return;
}
}
Map<String, byte[]> bytesMap = new HashMap<String, byte[]>();
for (String path : paths) {
RandomAccessFile f = null;
try {
f = new RandomAccessFile(path, "r");
final byte[] bytes = new byte[(int) f.length()];
f.readFully(bytes);
final String clazzName = readClassName(bytes);
bytesMap.put(clazzName, bytes);
} catch (Exception e) {
logger.warn("load class file failed: "+path, e);
process.end(-1, "load class file failed: " +path+", error: " + e);
return;
} finally {
if (f != null) {
try {
f.close();
} catch (IOException e) {
// ignore
}
}
}
}
if (bytesMap.size() != paths.size()) {
process.end(-1, "paths may contains same class name!");
return;
}
List<ClassDefinition> definitions = new ArrayList<ClassDefinition>();
for (Class<?> clazz : inst.getAllLoadedClasses()) {
if (bytesMap.containsKey(clazz.getName())) {
if (hashCode == null && classLoaderClass != null) {
List<ClassLoader> matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst, classLoaderClass);
if (matchedClassLoaders.size() == 1) {
hashCode = Integer.toHexString(matchedClassLoaders.get(0).hashCode());
} else if (matchedClassLoaders.size() > 1) {
Collection<ClassLoaderVO> classLoaderVOList = ClassUtils.createClassLoaderVOList(matchedClassLoaders);
RedefineModel classredefineModel = new RedefineModel()
.setClassLoaderClass(classLoaderClass)
.setMatchedClassLoaders(classLoaderVOList);
process.appendResult(classredefineModel);
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;
}
}
ClassLoader classLoader = clazz.getClassLoader();
if (classLoader != null && hashCode != null && !Integer.toHexString(classLoader.hashCode()).equals(hashCode)) {
continue;
}
definitions.add(new ClassDefinition(clazz, bytesMap.get(clazz.getName())));
redefineModel.addRedefineClass(clazz.getName());
logger.info("Try redefine class name: {}, ClassLoader: {}", clazz.getName(), clazz.getClassLoader());
}
}
try {
if (definitions.isEmpty()) {
process.end(-1, "These classes are not found in the JVM and may not be loaded: " + bytesMap.keySet());
return;
}
inst.redefineClasses(definitions.toArray(new ClassDefinition[0]));
process.appendResult(redefineModel);
process.end();
} catch (Throwable e) {
String message = "redefine error! " + e.toString();
logger.error(message, e);
process.end(-1, message);
}
}
private static String readClassName(final byte[] bytes) {
return new ClassReader(bytes).getClassName().replace("/", ".");
}
@Override
public void complete(Completion completion) {
if (!CompletionUtils.completeFilePath(completion)) {
super.complete(completion);
}
}
}
package com.taobao.arthas.core.command.klass100;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.alibaba.deps.org.objectweb.asm.ClassReader;
import com.taobao.arthas.core.advisor.TransformerManager;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.model.ClassLoaderVO;
import com.taobao.arthas.core.command.model.RetransformModel;
import com.taobao.arthas.core.server.ArthasBootstrap;
import com.taobao.arthas.core.shell.cli.CliToken;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.cli.CompletionUtils;
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.middleware.cli.annotations.Argument;
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;
/**
*
* Retransform Classes.
*
* @author hengyunabc 2021-01-05
* @see java.lang.instrument.Instrumentation#retransformClasses(Class...)
*/
@Name("retransform")
@Summary("Retransform classes. @see Instrumentation#retransformClasses(Class...)")
@Description(Constants.EXAMPLE + " retransform /tmp/Test.class\n"
+ " retransform -l \n"
+ " retransform -d 1 # delete retransform entry\n"
+ " retransform --deleteAll # delete all retransform entries\n"
+ " retransform --classPattern demo.* # triger retransform classes\n"
+ " retransform -c 327a647b /tmp/Test.class /tmp/Test\\$Inner.class \n"
+ " retransform --classLoaderClass 'sun.misc.Launcher$AppClassLoader' /tmp/Test.class\n"
+ Constants.WIKI + Constants.WIKI_HOME
+ "retransform")
public class RetransformCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(RetransformCommand.class);
private static final int MAX_FILE_SIZE = 10 * 1024 * 1024;
private static volatile List<RetransformEntry> retransformEntries = new ArrayList<RetransformEntry>();
private static volatile ClassFileTransformer transformer = null;
private String hashCode;
private String classLoaderClass;
private List<String> paths;
private boolean list;
private int delete = -1;
private boolean deleteAll;
private String classPattern;
private int limit;
@Option(shortName = "l", longName = "list", flag = true)
@Description("list all retransform entry.")
public void setList(boolean list) {
this.list = list;
}
@Option(shortName = "d", longName = "delete")
@Description("delete retransform entry by id.")
public void setDelete(int delete) {
this.delete = delete;
}
@Option(longName = "deleteAll", flag = true)
@Description("delete all retransform entries.")
public void setDeleteAll(boolean deleteAll) {
this.deleteAll = deleteAll;
}
@Option(longName = "classPattern")
@Description("trigger retransform matched classes by class pattern.")
public void setClassPattern(String classPattern) {
this.classPattern = classPattern;
}
@Option(shortName = "c", longName = "classloader")
@Description("classLoader hashcode")
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;
}
@Argument(argName = "classfilePaths", index = 0, required = false)
@Description(".class file paths")
public void setPaths(List<String> paths) {
this.paths = paths;
}
@Option(longName = "limit")
@Description("The limit of dump classes size, default value is 50")
@DefaultValue("50")
public void setLimit(int limit) {
this.limit = limit;
}
private static void initTransformer() {
if (transformer != null) {
return;
} else {
synchronized (RetransformCommand.class) {
if (transformer == null) {
transformer = new RetransformClassFileTransformer();
TransformerManager transformerManager = ArthasBootstrap.getInstance().getTransformerManager();
transformerManager.addRetransformer(transformer);
}
}
}
}
@Override
public void process(CommandProcess process) {
initTransformer();
RetransformModel retransformModel = new RetransformModel();
Instrumentation inst = process.session().getInstrumentation();
if (this.list) {
List<RetransformEntry> retransformEntryList = allRetransformEntries();
retransformModel.setRetransformEntries(retransformEntryList);
process.appendResult(retransformModel);
process.end();
return;
} else if (this.deleteAll) {
deleteAllRetransformEntry();
process.appendResult(retransformModel);
process.end();
return;
} else if (this.delete > 0) {
deleteRetransformEntry(this.delete);
process.end();
return;
} else if (this.classPattern != null) {
Set<Class<?>> searchClass = SearchUtils.searchClass(inst, classPattern, false, this.hashCode);
if (searchClass.isEmpty()) {
process.end(-1, "These classes are not found in the JVM and may not be loaded: " + classPattern);
return;
}
if (searchClass.size() > limit) {
process.end(-1, "match classes size: " + searchClass.size() + ", more than limit: " + limit
+ ", It is recommended to use a more precise class pattern.");
}
try {
inst.retransformClasses(searchClass.toArray(new Class[0]));
for (Class<?> clazz : searchClass) {
retransformModel.addRetransformClass(clazz.getName());
}
process.appendResult(retransformModel);
process.end();
return;
} catch (Throwable e) {
String message = "retransform error! " + e.toString();
logger.error(message, e);
process.end(-1, message);
return;
}
}
for (String path : paths) {
File file = new File(path);
if (!file.exists()) {
process.end(-1, "file does not exist, path:" + path);
return;
}
if (!file.isFile()) {
process.end(-1, "not a normal file, path:" + path);
return;
}
if (file.length() >= MAX_FILE_SIZE) {
process.end(-1, "file size: " + file.length() + " >= " + MAX_FILE_SIZE + ", path: " + path);
return;
}
}
Map<String, byte[]> bytesMap = new HashMap<String, byte[]>();
for (String path : paths) {
RandomAccessFile f = null;
try {
f = new RandomAccessFile(path, "r");
final byte[] bytes = new byte[(int) f.length()];
f.readFully(bytes);
final String clazzName = readClassName(bytes);
bytesMap.put(clazzName, bytes);
} catch (Exception e) {
logger.warn("load class file failed: " + path, e);
process.end(-1, "load class file failed: " + path + ", error: " + e);
return;
} finally {
if (f != null) {
try {
f.close();
} catch (IOException e) {
// ignore
}
}
}
}
if (bytesMap.size() != paths.size()) {
process.end(-1, "paths may contains same class name!");
return;
}
List<RetransformEntry> retransformEntryList = new ArrayList<RetransformEntry>();
List<Class<?>> classList = new ArrayList<Class<?>>();
for (Class<?> clazz : inst.getAllLoadedClasses()) {
if (bytesMap.containsKey(clazz.getName())) {
if (hashCode == null && classLoaderClass != null) {
List<ClassLoader> matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst,
classLoaderClass);
if (matchedClassLoaders.size() == 1) {
hashCode = Integer.toHexString(matchedClassLoaders.get(0).hashCode());
} else if (matchedClassLoaders.size() > 1) {
Collection<ClassLoaderVO> classLoaderVOList = ClassUtils
.createClassLoaderVOList(matchedClassLoaders);
retransformModel.setClassLoaderClass(classLoaderClass)
.setMatchedClassLoaders(classLoaderVOList);
process.appendResult(retransformModel);
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;
}
}
ClassLoader classLoader = clazz.getClassLoader();
if (classLoader != null && hashCode != null
&& !Integer.toHexString(classLoader.hashCode()).equals(hashCode)) {
continue;
}
RetransformEntry retransformEntry = new RetransformEntry(clazz.getName(), bytesMap.get(clazz.getName()),
hashCode, classLoaderClass);
retransformEntryList.add(retransformEntry);
classList.add(clazz);
retransformModel.addRetransformClass(clazz.getName());
logger.info("Try retransform class name: {}, ClassLoader: {}", clazz.getName(), clazz.getClassLoader());
}
}
try {
if (retransformEntryList.isEmpty()) {
process.end(-1, "These classes are not found in the JVM and may not be loaded: " + bytesMap.keySet());
return;
}
addRetransformEntry(retransformEntryList);
inst.retransformClasses(classList.toArray(new Class[0]));
process.appendResult(retransformModel);
process.end();
} catch (Throwable e) {
String message = "retransform error! " + e.toString();
logger.error(message, e);
process.end(-1, message);
}
}
private static String readClassName(final byte[] bytes) {
return new ClassReader(bytes).getClassName().replace('/', '.');
}
@Override
public void complete(Completion completion) {
List<CliToken> tokens = completion.lineTokens();
if (CompletionUtils.shouldCompleteOption(completion, "--classPattern")) {
CompletionUtils.completeClassName(completion);
return;
}
for (CliToken token : tokens) {
String tokenStr = token.value();
if (tokenStr != null && tokenStr.startsWith("-")) {
super.complete(completion);
return;
}
}
// 最后,没有有 - 开头的,才尝试补全 file path
if (!CompletionUtils.completeFilePath(completion)) {
super.complete(completion);
}
}
public static class RetransformEntry {
private static final AtomicInteger counter = new AtomicInteger(0);
private int id;
private String className;
private byte[] bytes;
private String hashCode;
private String classLoaderClass;
/**
* 被 transform 触发次数
*/
private int transformCount = 0;
public RetransformEntry(String className, byte[] bytes, String hashCode, String classLoaderClass) {
id = counter.incrementAndGet();
this.className = className;
this.bytes = bytes;
this.hashCode = hashCode;
this.classLoaderClass = classLoaderClass;
}
public void incTransformCount() {
transformCount++;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getTransformCount() {
return transformCount;
}
public void setTransformCount(int transformCount) {
this.transformCount = transformCount;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
public String getHashCode() {
return hashCode;
}
public void setHashCode(String hashCode) {
this.hashCode = hashCode;
}
public String getClassLoaderClass() {
return classLoaderClass;
}
public void setClassLoaderClass(String classLoaderClass) {
this.classLoaderClass = classLoaderClass;
}
}
public static synchronized void addRetransformEntry(List<RetransformEntry> retransformEntryList) {
List<RetransformEntry> tmp = new ArrayList<RetransformEntry>();
tmp.addAll(retransformEntries);
tmp.addAll(retransformEntryList);
Collections.sort(tmp, new Comparator<RetransformEntry>() {
@Override
public int compare(RetransformEntry entry1, RetransformEntry entry2) {
return entry1.getId() - entry2.getId();
}
});
retransformEntries = tmp;
}
public static synchronized RetransformEntry deleteRetransformEntry(int id) {
RetransformEntry result = null;
List<RetransformEntry> tmp = new ArrayList<RetransformEntry>();
for (RetransformEntry entry : retransformEntries) {
if (entry.getId() != id) {
tmp.add(entry);
} else {
result = entry;
}
}
retransformEntries = tmp;
return result;
}
public static List<RetransformEntry> allRetransformEntries() {
return retransformEntries;
}
public static synchronized void deleteAllRetransformEntry() {
retransformEntries = new ArrayList<RetransformEntry>();
}
static class RetransformClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className == null) {
return null;
}
className = className.replace('/', '.');
List<RetransformEntry> allRetransformEntries = allRetransformEntries();
// 倒序,因为要执行的配置生效
ListIterator<RetransformEntry> listIterator = allRetransformEntries
.listIterator(allRetransformEntries.size());
while (listIterator.hasPrevious()) {
RetransformEntry retransformEntry = listIterator.previous();
int id = retransformEntry.getId();
// 判断类名是否一致
boolean updateFlag = false;
// 类名一致,则看是否要比较 loader,如果不需要比较 loader,则认为成功
if (className.equals(retransformEntry.getClassName())) {
if (retransformEntry.getClassLoaderClass() != null || retransformEntry.getHashCode() != null) {
updateFlag = isLoaderMatch(retransformEntry, loader);
} else {
updateFlag = true;
}
}
if (updateFlag) {
logger.info("RetransformCommand match class: {}, id: {}, classLoaderClass: {}, hashCode: {}",
className, id, retransformEntry.getClassLoaderClass(), retransformEntry.getHashCode());
retransformEntry.incTransformCount();
return retransformEntry.getBytes();
}
}
return null;
}
private boolean isLoaderMatch(RetransformEntry retransformEntry, ClassLoader loader) {
if (loader == null) {
return false;
}
if (retransformEntry.getClassLoaderClass() != null) {
if (loader.getClass().getName().equals(retransformEntry.getClassLoaderClass())) {
return true;
}
}
if (retransformEntry.getHashCode() != null) {
String hashCode = Integer.toHexString(loader.hashCode());
if (hashCode.equals(retransformEntry.getHashCode())) {
return true;
}
}
return false;
}
}
}
package com.taobao.arthas.core.command.klass100;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.model.ClassDetailVO;
import com.taobao.arthas.core.command.model.SearchClassModel;
import com.taobao.arthas.core.command.model.RowAffectModel;
import com.taobao.arthas.core.command.model.ClassLoaderVO;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.cli.CompletionUtils;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.util.ClassUtils;
import com.taobao.arthas.core.util.ClassLoaderUtils;
import com.taobao.arthas.core.util.ResultUtils;
import com.taobao.arthas.core.util.SearchUtils;
import com.taobao.arthas.core.util.StringUtils;
import com.taobao.arthas.core.util.affect.RowAffect;
import com.taobao.middleware.cli.annotations.Argument;
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;
/**
* 展示类信息
*
* @author vlinux
*/
@Name("sc")
@Summary("Search all the classes loaded by JVM")
@Description(Constants.EXAMPLE +
" sc -d org.apache.commons.lang.StringUtils\n" +
" sc -d org/apache/commons/lang/StringUtils\n" +
" sc -d *StringUtils\n" +
" sc -d -f org.apache.commons.lang.StringUtils\n" +
" sc -E org\\\\.apache\\\\.commons\\\\.lang\\\\.StringUtils\n" +
Constants.WIKI + Constants.WIKI_HOME + "sc")
public class SearchClassCommand extends AnnotatedCommand {
private String classPattern;
private boolean isDetail = false;
private boolean isField = false;
private boolean isRegEx = false;
private String hashCode = null;
private String classLoaderClass;
private Integer expand;
private int numberOfLimit = 100;
@Argument(argName = "class-pattern", index = 0)
@Description("Class name pattern, use either '.' or '/' as separator")
public void setClassPattern(String classPattern) {
this.classPattern = classPattern;
}
@Option(shortName = "d", longName = "details", flag = true)
@Description("Display the details of class")
public void setDetail(boolean detail) {
isDetail = detail;
}
@Option(shortName = "f", longName = "field", flag = true)
@Description("Display all the member variables")
public void setField(boolean field) {
isField = field;
}
@Option(shortName = "E", longName = "regex", flag = true)
@Description("Enable regular expression to match (wildcard matching by default)")
public void setRegEx(boolean regEx) {
isRegEx = regEx;
}
@Option(shortName = "x", longName = "expand")
@Description("Expand level of object (0 by default)")
public void setExpand(Integer 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(shortName = "n", longName = "limits")
@Description("Maximum number of matching classes with details (100 by default)")
public void setNumberOfLimit(int numberOfLimit) {
this.numberOfLimit = numberOfLimit;
}
@Override
public void process(final CommandProcess process) {
// TODO: null check
RowAffect affect = new RowAffect();
Instrumentation inst = process.session().getInstrumentation();
if (hashCode == null && classLoaderClass != null) {
List<ClassLoader> matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst, classLoaderClass);
if (matchedClassLoaders.size() == 1) {
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, classPattern, isRegEx, hashCode));
Collections.sort(matchedClasses, new Comparator<Class<?>>() {
@Override
public int compare(Class<?> c1, Class<?> c2) {
return StringUtils.classname(c1).compareTo(StringUtils.classname(c2));
}
});
if (isDetail) {
if (numberOfLimit > 0 && matchedClasses.size() > numberOfLimit) {
process.end(-1, "The number of matching classes is greater than : " + numberOfLimit+". \n" +
"Please specify a more accurate 'class-patten' or use the parameter '-n' to change the maximum number of matching classes.");
return;
}
for (Class<?> clazz : matchedClasses) {
ClassDetailVO classInfo = ClassUtils.createClassInfo(clazz, isField, expand);
process.appendResult(new SearchClassModel(classInfo, isDetail, isField));
}
} else {
int pageSize = 256;
ResultUtils.processClassNames(matchedClasses, pageSize, new ResultUtils.PaginationHandler<List<String>>() {
@Override
public boolean handle(List<String> classNames, int segment) {
process.appendResult(new SearchClassModel(classNames, segment));
return true;
}
});
}
affect.rCnt(matchedClasses.size());
process.appendResult(new RowAffectModel(affect));
process.end();
}
@Override
public void complete(Completion completion) {
if (!CompletionUtils.completeClassName(completion)) {
super.complete(completion);
}
}
}
package com.taobao.arthas.core.command.klass100;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.Collection;
import java.util.List;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.model.SearchMethodModel;
import com.taobao.arthas.core.command.model.MethodVO;
import com.taobao.arthas.core.command.model.RowAffectModel;
import com.taobao.arthas.core.command.model.ClassLoaderVO;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.cli.CompletionUtils;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.util.ClassUtils;
import com.taobao.arthas.core.util.ClassLoaderUtils;
import com.taobao.arthas.core.util.SearchUtils;
import com.taobao.arthas.core.util.StringUtils;
import com.taobao.arthas.core.util.affect.RowAffect;
import com.taobao.arthas.core.util.matcher.Matcher;
import com.taobao.arthas.core.util.matcher.RegexMatcher;
import com.taobao.arthas.core.util.matcher.WildcardMatcher;
import com.taobao.middleware.cli.annotations.Argument;
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;
/**
* 展示方法信息
*
* @author vlinux
* @author hengyunabc 2019-02-13
*/
@Name("sm")
@Summary("Search the method of classes loaded by JVM")
@Description(Constants.EXAMPLE +
" sm java.lang.String\n" +
" sm -d org.apache.commons.lang.StringUtils\n" +
" sm -d org/apache/commons/lang/StringUtils\n" +
" sm *StringUtils *\n" +
" sm -Ed org\\\\.apache\\\\.commons\\\\.lang\\.StringUtils .*\n" +
Constants.WIKI + Constants.WIKI_HOME + "sm")
public class SearchMethodCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(SearchMethodCommand.class);
private String classPattern;
private String methodPattern;
private String hashCode = null;
private String classLoaderClass;
private boolean isDetail = false;
private boolean isRegEx = false;
private int numberOfLimit = 100;
@Argument(argName = "class-pattern", index = 0)
@Description("Class name pattern, use either '.' or '/' as separator")
public void setClassPattern(String classPattern) {
this.classPattern = classPattern;
}
@Argument(argName = "method-pattern", index = 1, required = false)
@Description("Method name pattern")
public void setMethodPattern(String methodPattern) {
this.methodPattern = methodPattern;
}
@Option(shortName = "d", longName = "details", flag = true)
@Description("Display the details of method")
public void setDetail(boolean detail) {
isDetail = detail;
}
@Option(shortName = "E", longName = "regex", flag = true)
@Description("Enable regular expression to match (wildcard matching by default)")
public void setRegEx(boolean regEx) {
isRegEx = regEx;
}
@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(shortName = "n", longName = "limits")
@Description("Maximum number of matching classes (100 by default)")
public void setNumberOfLimit(int numberOfLimit) {
this.numberOfLimit = numberOfLimit;
}
@Override
public void process(CommandProcess process) {
RowAffect affect = new RowAffect();
Instrumentation inst = process.session().getInstrumentation();
Matcher<String> methodNameMatcher = methodNameMatcher();
if (hashCode == null && classLoaderClass != null) {
List<ClassLoader> matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst, classLoaderClass);
if (matchedClassLoaders.size() == 1) {
hashCode = Integer.toHexString(matchedClassLoaders.get(0).hashCode());
} else if (matchedClassLoaders.size() > 1) {
Collection<ClassLoaderVO> classLoaderVOList = ClassUtils.createClassLoaderVOList(matchedClassLoaders);
SearchMethodModel searchmethodModel = new SearchMethodModel()
.setClassLoaderClass(classLoaderClass)
.setMatchedClassLoaders(classLoaderVOList);
process.appendResult(searchmethodModel);
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;
}
}
Set<Class<?>> matchedClasses = SearchUtils.searchClass(inst, classPattern, isRegEx, hashCode);
if (numberOfLimit > 0 && matchedClasses.size() > numberOfLimit) {
process.end(-1, "The number of matching classes is greater than : " + numberOfLimit+". \n" +
"Please specify a more accurate 'class-patten' or use the parameter '-n' to change the maximum number of matching classes.");
return;
}
for (Class<?> clazz : matchedClasses) {
try {
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if (!methodNameMatcher.matching("<init>")) {
continue;
}
MethodVO methodInfo = ClassUtils.createMethodInfo(constructor, clazz, isDetail);
process.appendResult(new SearchMethodModel(methodInfo, isDetail));
affect.rCnt(1);
}
for (Method method : clazz.getDeclaredMethods()) {
if (!methodNameMatcher.matching(method.getName())) {
continue;
}
MethodVO methodInfo = ClassUtils.createMethodInfo(method, clazz, isDetail);
process.appendResult(new SearchMethodModel(methodInfo, isDetail));
affect.rCnt(1);
}
} catch (Error e) {
//print failed className
String msg = String.format("process class failed: %s, error: %s", clazz.getName(), e.toString());
logger.error(msg, e);
process.end(1, msg);
return;
}
}
process.appendResult(new RowAffectModel(affect));
process.end();
}
private Matcher<String> methodNameMatcher() {
// auto fix default methodPattern
if (StringUtils.isBlank(methodPattern)) {
methodPattern = isRegEx ? ".*" : "*";
}
return isRegEx ? new RegexMatcher(methodPattern) : new WildcardMatcher(methodPattern);
}
@Override
public void complete(Completion completion) {
int argumentIndex = CompletionUtils.detectArgumentIndex(completion);
if (argumentIndex == 1) {
if (!CompletionUtils.completeClassName(completion)) {
super.complete(completion);
}
return;
} else if (argumentIndex == 2) {
if (!CompletionUtils.completeMethodName(completion)) {
super.complete(completion);
}
return;
}
super.complete(completion);
}
}
package com.taobao.arthas.core.command.logger;
import com.alibaba.deps.org.objectweb.asm.ClassReader;
import com.alibaba.deps.org.objectweb.asm.ClassVisitor;
import com.alibaba.deps.org.objectweb.asm.ClassWriter;
import com.alibaba.deps.org.objectweb.asm.commons.ClassRemapper;
import com.alibaba.deps.org.objectweb.asm.commons.SimpleRemapper;
/**
*
* @author hengyunabc 2019-09-23
*
*/
public class AsmRenameUtil {
public static byte[] renameClass(byte[] bytes, final String oldName, final String newName) {
ClassReader reader = new ClassReader(bytes);
ClassWriter writer = new ClassWriter(reader, 0);
final String internalOldName = oldName.replace('.', '/');
final String internalNewName = newName.replace('.', '/');
// ClassVisitor visitor = new ClassRemapper(writer, new Remapper() {
//
// @Override
// public String mapType(String internalName) {
// if (internalName.equals(internalOldName)) {
// return internalNewName;
// } else {
// return super.mapType(internalName);
// }
// }
//
// });
ClassVisitor visitor = new ClassRemapper(writer, new SimpleRemapper(internalOldName, internalNewName));
reader.accept(visitor, 0);
return writer.toByteArray();
}
}
package com.taobao.arthas.core.command.logger;
import java.lang.reflect.Field;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.AsyncAppender;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.appender.FileAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
/**
*
* @author hengyunabc 2019-09-20
*
*/
public class Log4j2Helper {
private static boolean Log4j2 = false;
private static Field configField = null;
static {
try {
Class<?> loggerClass = Log4j2Helper.class.getClassLoader().loadClass("org.apache.logging.log4j.Logger");
// 这里可能会加载到其它上游ClassLoader的log4j2,因此需要判断是否当前classloader
if (loggerClass.getClassLoader().equals(Log4j2Helper.class.getClassLoader())) {
Log4j2 = true;
}
try {
configField = LoggerConfig.class.getDeclaredField("config");
configField.setAccessible(true);
} catch (Throwable e) {
// ignore
}
} catch (Throwable t) {
}
}
public static boolean hasLength(String str) {
return (str != null && !str.isEmpty());
}
private static LoggerConfig getLoggerConfig(String name) {
if (!hasLength(name) || LoggerConfig.ROOT.equalsIgnoreCase(name)) {
name = LogManager.ROOT_LOGGER_NAME;
}
return getLoggerContext().getConfiguration().getLoggers().get(name);
}
private static LoggerContext getLoggerContext() {
return (LoggerContext) LogManager.getContext(false);
}
public static Boolean updateLevel(String loggerName, String logLevel) {
if (Log4j2) {
Level level = Level.getLevel(logLevel.toUpperCase());
if (level == null) {
return null;
}
LoggerConfig loggerConfig = getLoggerConfig(loggerName);
if (loggerConfig == null) {
loggerConfig = new LoggerConfig(loggerName, level, true);
getLoggerContext().getConfiguration().addLogger(loggerName, loggerConfig);
} else {
loggerConfig.setLevel(level);
}
getLoggerContext().updateLoggers();
return Boolean.TRUE;
}
return null;
}
public static Map<String, Map<String, Object>> getLoggers(String name, boolean includeNoAppender) {
Map<String, Map<String, Object>> loggerInfoMap = new HashMap<String, Map<String, Object>>();
if (!Log4j2) {
return loggerInfoMap;
}
Configuration configuration = getLoggerContext().getConfiguration();
if (name != null && !name.trim().isEmpty()) {
LoggerConfig loggerConfig = configuration.getLoggerConfig(name);
if (loggerConfig == null) {
return loggerInfoMap;
}
// 排掉非root时,获取到root的logger config
if (!name.equalsIgnoreCase(LoggerConfig.ROOT) && isEmpty(loggerConfig.getName())) {
return loggerInfoMap;
}
loggerInfoMap.put(name, doGetLoggerInfo(loggerConfig));
} else {
// 获取所有logger时,如果没有appender则忽略
Map<String, LoggerConfig> loggers = configuration.getLoggers();
if (loggers != null) {
for (Entry<String, LoggerConfig> entry : loggers.entrySet()) {
LoggerConfig loggerConfig = entry.getValue();
if (!includeNoAppender) {
if (!loggerConfig.getAppenders().isEmpty()) {
loggerInfoMap.put(entry.getKey(), doGetLoggerInfo(entry.getValue()));
}
} else {
loggerInfoMap.put(entry.getKey(), doGetLoggerInfo(entry.getValue()));
}
}
}
}
return loggerInfoMap;
}
private static Object getConfigField(LoggerConfig loggerConfig) {
try {
if (configField != null) {
return configField.get(loggerConfig);
}
} catch (Throwable e) {
// ignore
}
return null;
}
private static Map<String, Object> doGetLoggerInfo(LoggerConfig loggerConfig) {
Map<String, Object> info = new HashMap<String, Object>();
String name = loggerConfig.getName();
if (name == null || name.trim().isEmpty()) {
name = LoggerConfig.ROOT;
}
info.put(LoggerHelper.name, name);
info.put(LoggerHelper.clazz, loggerConfig.getClass());
CodeSource codeSource = loggerConfig.getClass().getProtectionDomain().getCodeSource();
if (codeSource != null) {
info.put(LoggerHelper.codeSource, codeSource.getLocation());
}
Object config = getConfigField(loggerConfig);
if (config != null) {
info.put(LoggerHelper.config, config);
}
info.put(LoggerHelper.additivity, loggerConfig.isAdditive());
Level level = loggerConfig.getLevel();
if (level != null) {
info.put(LoggerHelper.level, level.toString());
}
List<Map<String, Object>> result = doGetLoggerAppenders(loggerConfig);
info.put(LoggerHelper.appenders, result);
return info;
}
private static List<Map<String, Object>> doGetLoggerAppenders(LoggerConfig loggerConfig) {
List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
Map<String, Appender> appenders = loggerConfig.getAppenders();
for (Entry<String, Appender> entry : appenders.entrySet()) {
Map<String, Object> info = new HashMap<String, Object>();
Appender appender = entry.getValue();
info.put(LoggerHelper.name, appender.getName());
info.put(LoggerHelper.clazz, appender.getClass());
result.add(info);
if (appender instanceof FileAppender) {
info.put(LoggerHelper.file, ((FileAppender) appender).getFileName());
} else if (appender instanceof ConsoleAppender) {
info.put(LoggerHelper.target, ((ConsoleAppender) appender).getTarget());
} else if (appender instanceof AsyncAppender) {
AsyncAppender asyncAppender = ((AsyncAppender) appender);
String[] appenderRefStrings = asyncAppender.getAppenderRefStrings();
info.put(LoggerHelper.blocking, asyncAppender.isBlocking());
info.put(LoggerHelper.appenderRef, Arrays.asList(appenderRefStrings));
}
}
return result;
}
private static boolean isEmpty(Object str) {
return str == null || "".equals(str);
}
}
package com.taobao.arthas.core.command.logger;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Appender;
import org.apache.log4j.AsyncAppender;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
/**
*
* @author hengyunabc 2019-09-06
*
*/
public class Log4jHelper {
private static boolean Log4j = false;
static {
try {
Class<?> loggerClass = Log4jHelper.class.getClassLoader().loadClass("org.apache.log4j.Logger");
// 这里可能会加载到其它上游ClassLoader的log4j,因此需要判断是否当前classloader
if (loggerClass.getClassLoader().equals(Log4jHelper.class.getClassLoader())) {
Log4j = true;
}
} catch (Throwable t) {
}
}
public static Boolean updateLevel(String name, String level) {
if (Log4j) {
Level l = Level.toLevel(level, Level.ERROR);
Logger logger = LogManager.getLoggerRepository().exists(name);
if (logger != null) {
logger.setLevel(l);
return true;
} else {
Logger root = LogManager.getLoggerRepository().getRootLogger();
if (root.getName().equals(name)) {
root.setLevel(l);
return true;
}
}
return false;
}
return null;
}
public static Map<String, Map<String, Object>> getLoggers(String name, boolean includeNoAppender) {
Map<String, Map<String, Object>> loggerInfoMap = new HashMap<String, Map<String, Object>>();
if (!Log4j) {
return loggerInfoMap;
}
if (name != null && !name.trim().isEmpty()) {
Logger logger = LogManager.getLoggerRepository().exists(name);
if (logger != null) {
loggerInfoMap.put(name, doGetLoggerInfo(logger));
}
} else {
// 获取所有logger时,如果没有appender则忽略
@SuppressWarnings("unchecked")
Enumeration<Logger> loggers = LogManager.getLoggerRepository().getCurrentLoggers();
if (loggers != null) {
while (loggers.hasMoreElements()) {
Logger logger = loggers.nextElement();
Map<String, Object> info = doGetLoggerInfo(logger);
if (!includeNoAppender) {
List<?> appenders = (List<?>) info.get(LoggerHelper.appenders);
if (appenders != null && !appenders.isEmpty()) {
loggerInfoMap.put(logger.getName(), info);
}
} else {
loggerInfoMap.put(logger.getName(), info);
}
}
}
Logger root = LogManager.getLoggerRepository().getRootLogger();
if (root != null) {
Map<String, Object> info = doGetLoggerInfo(root);
if (!includeNoAppender) {
List<?> appenders = (List<?>) info.get(LoggerHelper.appenders);
if (appenders != null && !appenders.isEmpty()) {
loggerInfoMap.put(root.getName(), info);
}
} else {
loggerInfoMap.put(root.getName(), info);
}
}
}
return loggerInfoMap;
}
private static Map<String, Object> doGetLoggerInfo(Logger logger) {
Map<String, Object> info = new HashMap<String, Object>();
info.put(LoggerHelper.name, logger.getName());
info.put(LoggerHelper.clazz, logger.getClass());
CodeSource codeSource = logger.getClass().getProtectionDomain().getCodeSource();
if (codeSource != null) {
info.put(LoggerHelper.codeSource, codeSource.getLocation());
}
info.put(LoggerHelper.additivity, logger.getAdditivity());
Level level = logger.getLevel(), effectiveLevel = logger.getEffectiveLevel();
if (level != null) {
info.put(LoggerHelper.level, level.toString());
}
if (effectiveLevel != null) {
info.put(LoggerHelper.effectiveLevel, effectiveLevel.toString());
}
@SuppressWarnings("unchecked")
List<Map<String, Object>> result = doGetLoggerAppenders(logger.getAllAppenders());
info.put(LoggerHelper.appenders, result);
return info;
}
private static List<Map<String, Object>> doGetLoggerAppenders(Enumeration<Appender> appenders) {
List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
if (appenders == null) {
return result;
}
while (appenders.hasMoreElements()) {
Map<String, Object> info = new HashMap<String, Object>();
Appender appender = appenders.nextElement();
info.put(LoggerHelper.name, appender.getName());
info.put(LoggerHelper.clazz, appender.getClass());
result.add(info);
if (appender instanceof FileAppender) {
info.put(LoggerHelper.file, ((FileAppender) appender).getFile());
} else if (appender instanceof ConsoleAppender) {
info.put(LoggerHelper.target, ((ConsoleAppender) appender).getTarget());
} else if (appender instanceof AsyncAppender) {
@SuppressWarnings("unchecked")
Enumeration<Appender> appendersOfAsync = ((AsyncAppender) appender).getAllAppenders();
if (appendersOfAsync != null) {
List<Map<String, Object>> asyncs = doGetLoggerAppenders(appendersOfAsync);
// 标明异步appender
List<String> appenderRef = new ArrayList<String>();
for (Map<String, Object> a : asyncs) {
appenderRef.add((String) a.get(LoggerHelper.name));
result.add(a);
}
info.put(LoggerHelper.blocking, ((AsyncAppender) appender).getBlocking());
info.put(LoggerHelper.appenderRef, appenderRef);
}
}
}
return result;
}
}
package com.taobao.arthas.core.command.logger;
import java.lang.reflect.Field;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.ILoggerFactory;
import ch.qos.logback.classic.AsyncAppender;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.pattern.PatternLayoutBase;
/**
*
* @author hengyunabc 2019-09-06
*
*/
public class LogbackHelper {
private static boolean Logback = false;
private static Field headField, lengthOptionField;
private static ILoggerFactory loggerFactoryInstance;
static {
try {
Class<?> loggerClass = LogbackHelper.class.getClassLoader().loadClass("ch.qos.logback.classic.Logger");
// 这里可能会加载到应用中依赖的logback,因此需要判断classloader
if (loggerClass.getClassLoader().equals(LogbackHelper.class.getClassLoader())) {
ILoggerFactory loggerFactory = org.slf4j.LoggerFactory.getILoggerFactory();
if (loggerFactory instanceof LoggerContext) {
loggerFactoryInstance = loggerFactory;
headField = PatternLayoutBase.class.getDeclaredField("head");
headField.setAccessible(true);
lengthOptionField = ThrowableProxyConverter.class.getDeclaredField("lengthOption");
lengthOptionField.setAccessible(true);
Logback = true;
}
}
} catch (Throwable t) {
// ignore
}
}
public static Boolean updateLevel(String name, String level) {
if (Logback) {
try {
Level l = Level.toLevel(level, Level.ERROR);
LoggerContext loggerContext = (LoggerContext) loggerFactoryInstance;
Logger logger = loggerContext.exists(name);
if (logger != null) {
logger.setLevel(l);
return true;
}
} catch (Throwable t) {
// ignore
}
return false;
}
return null;
}
public static Map<String, Map<String, Object>> getLoggers(String name, boolean includeNoAppender) {
Map<String, Map<String, Object>> loggerInfoMap = new LinkedHashMap<String, Map<String, Object>>();
if (Logback) {
LoggerContext loggerContext = (LoggerContext) loggerFactoryInstance;
if (name != null && !name.trim().isEmpty()) {
Logger logger = loggerContext.exists(name);
if (logger != null) {
loggerInfoMap.put(name, doGetLoggerInfo(logger));
}
} else {
// 获取所有logger时,如果没有appender则忽略
List<Logger> loggers = loggerContext.getLoggerList();
for (Logger logger : loggers) {
Map<String, Object> info = doGetLoggerInfo(logger);
if (!includeNoAppender) {
List<?> appenders = (List<?>) info.get(LoggerHelper.appenders);
if (appenders != null && !appenders.isEmpty()) {
loggerInfoMap.put(logger.getName(), info);
}
} else {
loggerInfoMap.put(logger.getName(), info);
}
}
}
}
return loggerInfoMap;
}
private static Map<String, Object> doGetLoggerInfo(Logger logger) {
Map<String, Object> info = new LinkedHashMap<String, Object>();
info.put(LoggerHelper.name, logger.getName());
info.put(LoggerHelper.clazz, logger.getClass());
CodeSource codeSource = logger.getClass().getProtectionDomain().getCodeSource();
if (codeSource != null) {
info.put(LoggerHelper.codeSource, codeSource.getLocation());
}
info.put(LoggerHelper.additivity, logger.isAdditive());
Level level = logger.getLevel(), effectiveLevel = logger.getEffectiveLevel();
if (level != null) {
info.put(LoggerHelper.level, level.toString());
}
if (effectiveLevel != null) {
info.put(LoggerHelper.effectiveLevel, effectiveLevel.toString());
}
List<Map<String, Object>> result = doGetLoggerAppenders(logger.iteratorForAppenders());
info.put(LoggerHelper.appenders, result);
return info;
}
@SuppressWarnings("rawtypes")
private static List<Map<String, Object>> doGetLoggerAppenders(Iterator<Appender<ILoggingEvent>> appenders) {
List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
while (appenders.hasNext()) {
Map<String, Object> info = new LinkedHashMap<String, Object>();
Appender<ILoggingEvent> appender = appenders.next();
info.put(LoggerHelper.name, appender.getName());
info.put(LoggerHelper.clazz, appender.getClass());
if (appender instanceof FileAppender) {
info.put(LoggerHelper.file, ((FileAppender) appender).getFile());
} else if (appender instanceof AsyncAppender) {
AsyncAppender aa = (AsyncAppender) appender;
Iterator<Appender<ILoggingEvent>> iter = aa.iteratorForAppenders();
List<Map<String, Object>> asyncs = doGetLoggerAppenders(iter);
// 异步appender所 ref的 appender,参考: https://logback.qos.ch/manual/appenders.html
List<String> appenderRef = new ArrayList<String>();
for (Map<String, Object> a : asyncs) {
appenderRef.add((String) a.get(LoggerHelper.name));
result.add(a);
}
info.put(LoggerHelper.appenderRef, appenderRef);
info.put(LoggerHelper.blocking, !aa.isNeverBlock());
} else if (appender instanceof ConsoleAppender) {
info.put(LoggerHelper.target, ((ConsoleAppender) appender).getTarget());
}
result.add(info);
}
return result;
}
}
package com.taobao.arthas.core.command.logger;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
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.IOUtils;
import com.taobao.arthas.common.ReflectUtils;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.model.LoggerModel;
import com.taobao.arthas.core.command.model.ClassLoaderVO;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.util.ClassUtils;
import com.taobao.arthas.core.util.ClassLoaderUtils;
import com.taobao.arthas.core.util.StringUtils;
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;
/**
* logger command
*
* @author hengyunabc 2019-09-04
*/
//@formatter:off
@Name("logger")
@Summary("Print logger info, and update the logger level")
@Description("\nExamples:\n"
+ " logger\n"
+ " logger -c 327a647b\n"
+ " logger -c 327a647b --name ROOT --level debug\n"
+ " logger --include-no-appender\n"
+ Constants.WIKI + Constants.WIKI_HOME + "logger")
//@formatter:on
public class LoggerCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(LoggerCommand.class);
private static byte[] LoggerHelperBytes;
private static byte[] Log4jHelperBytes;
private static byte[] LogbackHelperBytes;
private static byte[] Log4j2HelperBytes;
private static Map<Class<?>, byte[]> classToBytesMap = new HashMap<Class<?>, byte[]>();
private static String arthasClassLoaderHash = ClassLoaderUtils
.classLoaderHash(LoggerCommand.class.getClassLoader());
static {
LoggerHelperBytes = loadClassBytes(LoggerHelper.class);
Log4jHelperBytes = loadClassBytes(Log4jHelper.class);
LogbackHelperBytes = loadClassBytes(LogbackHelper.class);
Log4j2HelperBytes = loadClassBytes(Log4j2Helper.class);
classToBytesMap.put(LoggerHelper.class, LoggerHelperBytes);
classToBytesMap.put(Log4jHelper.class, Log4jHelperBytes);
classToBytesMap.put(LogbackHelper.class, LogbackHelperBytes);
classToBytesMap.put(Log4j2Helper.class, Log4j2HelperBytes);
}
private String name;
private String hashCode;
private String classLoaderClass;
private String level;
/**
* include the loggers which don't have appenders, default false.
*/
private boolean includeNoAppender;
@Option(shortName = "n", longName = "name")
@Description("logger name")
public void setName(String name) {
this.name = name;
}
@Option(shortName = "c", longName = "classloader")
@Description("classLoader hashcode, if no value is set, default value is SystemClassLoader")
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(shortName = "l", longName = "level")
@Description("set logger level")
public void setLevel(String level) {
this.level = level;
}
@Option(longName = "include-no-appender", flag = true)
@Description("include the loggers which don't have appenders, default value false")
public void setHaveAppender(boolean includeNoAppender) {
this.includeNoAppender = includeNoAppender;
}
@Override
public void process(CommandProcess process) {
// 每个分支中调用process.end()结束执行
if (this.name != null && this.level != null) {
level(process);
} else {
loggers(process);
}
}
public void level(CommandProcess process) {
Instrumentation inst = process.session().getInstrumentation();
boolean result = false;
try {
Boolean updateResult = this.updateLevel(inst, Log4jHelper.class);
if (Boolean.TRUE.equals(updateResult)) {
result = true;
}
} catch (Throwable e) {
logger.error("logger command update log4j level error", e);
}
try {
Boolean updateResult = this.updateLevel(inst, LogbackHelper.class);
if (Boolean.TRUE.equals(updateResult)) {
result = true;
}
} catch (Throwable e) {
logger.error("logger command update logback level error", e);
}
try {
Boolean updateResult = this.updateLevel(inst, Log4j2Helper.class);
if (Boolean.TRUE.equals(updateResult)) {
result = true;
}
} catch (Throwable e) {
logger.error("logger command update log4j2 level error", e);
}
if (result) {
process.end(0, "Update logger level success.");
} else {
process.end(-1, "Update logger level fail. Try to specify the classloader with the -c option. Use `sc -d CLASSNAME` to find out the classloader hashcode.");
}
}
public void loggers(CommandProcess process) {
Map<ClassLoader, LoggerTypes> classLoaderLoggerMap = new LinkedHashMap<ClassLoader, LoggerTypes>();
for (Class<?> clazz : process.session().getInstrumentation().getAllLoadedClasses()) {
String className = clazz.getName();
ClassLoader classLoader = clazz.getClassLoader();
if (hashCode == null && classLoaderClass != null) {
Instrumentation inst = process.session().getInstrumentation();
List<ClassLoader> matchedClassLoaders = ClassLoaderUtils.getClassLoaderByClassName(inst, classLoaderClass);
if (matchedClassLoaders.size() == 1) {
hashCode = Integer.toHexString(matchedClassLoaders.get(0).hashCode());
} else if (matchedClassLoaders.size() > 1) {
Collection<ClassLoaderVO> classLoaderVOList = ClassUtils.createClassLoaderVOList(matchedClassLoaders);
LoggerModel loggerModel = new LoggerModel()
.setClassLoaderClass(classLoaderClass)
.setMatchedClassLoaders(classLoaderVOList);
process.appendResult(loggerModel);
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;
}
}
// if special classloader
if (this.hashCode != null && !this.hashCode.equals(StringUtils.classLoaderHash(clazz))) {
continue;
}
if (classLoader != null) {
LoggerTypes loggerTypes = classLoaderLoggerMap.get(classLoader);
if (loggerTypes == null) {
loggerTypes = new LoggerTypes();
classLoaderLoggerMap.put(classLoader, loggerTypes);
}
if ("org.apache.log4j.Logger".equals(className)) {
loggerTypes.addType(LoggerType.LOG4J);
} else if ("ch.qos.logback.classic.Logger".equals(className)) {
loggerTypes.addType(LoggerType.LOGBACK);
} else if ("org.apache.logging.log4j.Logger".equals(className)) {
loggerTypes.addType(LoggerType.LOG4J2);
}
}
}
for (Entry<ClassLoader, LoggerTypes> entry : classLoaderLoggerMap.entrySet()) {
ClassLoader classLoader = entry.getKey();
LoggerTypes loggerTypes = entry.getValue();
if (loggerTypes.contains(LoggerType.LOG4J)) {
Map<String, Map<String, Object>> loggerInfoMap = loggerInfo(classLoader, Log4jHelper.class);
process.appendResult(new LoggerModel(loggerInfoMap));
}
if (loggerTypes.contains(LoggerType.LOGBACK)) {
Map<String, Map<String, Object>> loggerInfoMap = loggerInfo(classLoader, LogbackHelper.class);
process.appendResult(new LoggerModel(loggerInfoMap));
}
if (loggerTypes.contains(LoggerType.LOG4J2)) {
Map<String, Map<String, Object>> loggerInfoMap = loggerInfo(classLoader, Log4j2Helper.class);
process.appendResult(new LoggerModel(loggerInfoMap));
}
}
process.end();
}
private static String helperClassNameWithClassLoader(ClassLoader classLoader, Class<?> helperClass) {
String classLoaderHash = ClassLoaderUtils.classLoaderHash(classLoader);
String className = helperClass.getName();
// if want to debug, change to return className
return className + arthasClassLoaderHash + classLoaderHash;
}
@SuppressWarnings("unchecked")
private Map<String, Map<String, Object>> loggerInfo(ClassLoader classLoader, Class<?> helperClass) {
Map<String, Map<String, Object>> loggers = Collections.emptyMap();
String helperClassName = helperClassNameWithClassLoader(classLoader, helperClass);
try {
classLoader.loadClass(helperClassName);
} catch (ClassNotFoundException e) {
try {
byte[] helperClassBytes = AsmRenameUtil.renameClass(classToBytesMap.get(helperClass),
helperClass.getName(), helperClassName);
ReflectUtils.defineClass(helperClassName, helperClassBytes, classLoader);
} catch (Throwable e1) {
logger.error("arthas loggger command try to define helper class error: " + helperClassName,
e1);
}
}
try {
Class<?> clazz = classLoader.loadClass(helperClassName);
Method getLoggersMethod = clazz.getMethod("getLoggers", new Class<?>[]{String.class, boolean.class});
loggers = (Map<String, Map<String, Object>>) getLoggersMethod.invoke(null,
new Object[]{name, includeNoAppender});
} catch (Throwable e) {
// ignore
}
//expose attributes to json: classloader, classloaderHash
for (Map<String, Object> loggerInfo : loggers.values()) {
Class clazz = (Class) loggerInfo.get(LoggerHelper.clazz);
loggerInfo.put(LoggerHelper.classLoader, getClassLoaderName(clazz.getClassLoader()));
loggerInfo.put(LoggerHelper.classLoaderHash, StringUtils.classLoaderHash(clazz));
List<Map<String, Object>> appenders = (List<Map<String, Object>>) loggerInfo.get(LoggerHelper.appenders);
for (Map<String, Object> appenderInfo : appenders) {
Class appenderClass = (Class) appenderInfo.get(LoggerHelper.clazz);
if (appenderClass != null) {
appenderInfo.put(LoggerHelper.classLoader, getClassLoaderName(appenderClass.getClassLoader()));
appenderInfo.put(LoggerHelper.classLoaderHash, StringUtils.classLoaderHash(appenderClass));
}
}
}
return loggers;
}
private String getClassLoaderName(ClassLoader classLoader) {
return classLoader == null ? null : classLoader.toString();
}
private Boolean updateLevel(Instrumentation inst, Class<?> helperClass) throws Exception {
ClassLoader classLoader = null;
if (hashCode == null) {
classLoader = ClassLoader.getSystemClassLoader();
} else {
classLoader = ClassLoaderUtils.getClassLoader(inst, hashCode);
}
Class<?> clazz = classLoader.loadClass(helperClassNameWithClassLoader(classLoader, helperClass));
Method updateLevelMethod = clazz.getMethod("updateLevel", new Class<?>[]{String.class, String.class});
return (Boolean) updateLevelMethod.invoke(null, new Object[]{this.name, this.level});
}
static enum LoggerType {
LOG4J, LOGBACK, LOG4J2
}
static class LoggerTypes {
Set<LoggerType> types = new HashSet<LoggerType>();
public Collection<LoggerType> types() {
return types;
}
public void addType(LoggerType type) {
types.add(type);
}
public boolean contains(LoggerType type) {
return types.contains(type);
}
}
private static byte[] loadClassBytes(Class<?> clazz) {
try {
InputStream stream = LoggerCommand.class.getClassLoader()
.getResourceAsStream(clazz.getName().replace('.', '/') + ".class");
return IOUtils.getBytes(stream);
} catch (IOException e) {
// ignore
return null;
}
}
}
package com.taobao.arthas.core.command.logger;
/**
*
* @author hengyunabc 2019-09-06
*
*/
public interface LoggerHelper {
public static final String clazz = "class";
public static final String classLoader = "classLoader";
public static final String classLoaderHash = "classLoaderHash";
public static final String codeSource = "codeSource";
// logger info
public static final String level = "level";
public static final String effectiveLevel = "effectiveLevel";
// log4j2 only
public static final String config = "config";
// type boolean
public static final String additivity = "additivity";
public static final String appenders = "appenders";
// appender info
public static final String name = "name";
public static final String file = "file";
public static final String blocking = "blocking";
// type List<String>
public static final String appenderRef = "appenderRef";
public static final String target = "target";
}
package com.taobao.arthas.core.command.model;
/**
* @author gongdewei 2020/4/3
*/
public class ArgumentVO {
private String argName;
private boolean required;
private boolean multiValued;
public ArgumentVO() {
}
public ArgumentVO(String argName, boolean required, boolean multiValued) {
this.argName = argName;
this.required = required;
this.multiValued = multiValued;
}
public String getArgName() {
return argName;
}
public void setArgName(String argName) {
this.argName = argName;
}
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
public boolean isMultiValued() {
return multiValued;
}
public void setMultiValued(boolean multiValued) {
this.multiValued = multiValued;
}
}
package com.taobao.arthas.core.command.model;
/**
*
* @author hengyunabc 2021-01-05
*
*/
public class Base64Model extends ResultModel {
private String content;
public Base64Model() {
}
public Base64Model(String content) {
this.content = content;
}
@Override
public String getType() {
return "base64";
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
package com.taobao.arthas.core.command.model;
import java.lang.management.ThreadInfo;
/**
* Thread blocking lock info, extract from ThreadUtil.
*
* @author gongdewei 2020/7/14
*/
public class BlockingLockInfo {
// the thread info that is holing this lock.
private ThreadInfo threadInfo = null;
// the associated LockInfo object
private int lockIdentityHashCode = 0;
// the number of thread that is blocked on this lock
private int blockingThreadCount = 0;
public BlockingLockInfo() {
}
public ThreadInfo getThreadInfo() {
return threadInfo;
}
public void setThreadInfo(ThreadInfo threadInfo) {
this.threadInfo = threadInfo;
}
public int getLockIdentityHashCode() {
return lockIdentityHashCode;
}
public void setLockIdentityHashCode(int lockIdentityHashCode) {
this.lockIdentityHashCode = lockIdentityHashCode;
}
public int getBlockingThreadCount() {
return blockingThreadCount;
}
public void setBlockingThreadCount(int blockingThreadCount) {
this.blockingThreadCount = blockingThreadCount;
}
}
package com.taobao.arthas.core.command.model;
import java.lang.management.LockInfo;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
/**
* Busy thread info, include ThreadInfo fields
*
* @author gongdewei 2020/4/26
*/
public class BusyThreadInfo extends ThreadVO {
private long blockedTime;
private long blockedCount;
private long waitedTime;
private long waitedCount;
private LockInfo lockInfo;
private String lockName;
private long lockOwnerId;
private String lockOwnerName;
private boolean inNative;
private boolean suspended;
private StackTraceElement[] stackTrace;
private MonitorInfo[] lockedMonitors;
private LockInfo[] lockedSynchronizers;
public BusyThreadInfo(ThreadVO thread, ThreadInfo threadInfo) {
this.setId(thread.getId());
this.setName(thread.getName());
this.setDaemon(thread.isDaemon());
this.setInterrupted(thread.isInterrupted());
this.setPriority(thread.getPriority());
this.setGroup(thread.getGroup());
this.setState(thread.getState());
this.setCpu(thread.getCpu());
this.setDeltaTime(thread.getDeltaTime());
this.setTime(thread.getTime());
//thread info
if (threadInfo != null) {
this.setLockInfo(threadInfo.getLockInfo());
this.setLockedMonitors(threadInfo.getLockedMonitors());
this.setLockedSynchronizers(threadInfo.getLockedSynchronizers());
this.setLockName(threadInfo.getLockName());
this.setLockOwnerId(threadInfo.getLockOwnerId());
this.setLockOwnerName(threadInfo.getLockOwnerName());
this.setStackTrace(threadInfo.getStackTrace());
this.setBlockedCount(threadInfo.getBlockedCount());
this.setBlockedTime(threadInfo.getBlockedTime());
this.setInNative(threadInfo.isInNative());
this.setSuspended(threadInfo.isSuspended());
this.setWaitedCount(threadInfo.getWaitedCount());
this.setWaitedTime(threadInfo.getWaitedTime());
}
}
public long getBlockedTime() {
return blockedTime;
}
public void setBlockedTime(long blockedTime) {
this.blockedTime = blockedTime;
}
public long getBlockedCount() {
return blockedCount;
}
public void setBlockedCount(long blockedCount) {
this.blockedCount = blockedCount;
}
public long getWaitedTime() {
return waitedTime;
}
public void setWaitedTime(long waitedTime) {
this.waitedTime = waitedTime;
}
public long getWaitedCount() {
return waitedCount;
}
public void setWaitedCount(long waitedCount) {
this.waitedCount = waitedCount;
}
public LockInfo getLockInfo() {
return lockInfo;
}
public void setLockInfo(LockInfo lockInfo) {
this.lockInfo = lockInfo;
}
public String getLockName() {
return lockName;
}
public void setLockName(String lockName) {
this.lockName = lockName;
}
public long getLockOwnerId() {
return lockOwnerId;
}
public void setLockOwnerId(long lockOwnerId) {
this.lockOwnerId = lockOwnerId;
}
public String getLockOwnerName() {
return lockOwnerName;
}
public void setLockOwnerName(String lockOwnerName) {
this.lockOwnerName = lockOwnerName;
}
public boolean isInNative() {
return inNative;
}
public void setInNative(boolean inNative) {
this.inNative = inNative;
}
public boolean isSuspended() {
return suspended;
}
public void setSuspended(boolean suspended) {
this.suspended = suspended;
}
public StackTraceElement[] getStackTrace() {
return stackTrace;
}
public void setStackTrace(StackTraceElement[] stackTrace) {
this.stackTrace = stackTrace;
}
public MonitorInfo[] getLockedMonitors() {
return lockedMonitors;
}
public void setLockedMonitors(MonitorInfo[] lockedMonitors) {
this.lockedMonitors = lockedMonitors;
}
public LockInfo[] getLockedSynchronizers() {
return lockedSynchronizers;
}
public void setLockedSynchronizers(LockInfo[] lockedSynchronizers) {
this.lockedSynchronizers = lockedSynchronizers;
}
}
package com.taobao.arthas.core.command.model;
/**
* Result model for CatCommand
* @author gongdewei 2020/5/11
*/
public class CatModel extends ResultModel implements Countable {
private String file;
private String content;
public CatModel() {
}
public CatModel(String file, String content) {
this.file = file;
this.content = content;
}
@Override
public String getType() {
return "cat";
}
public String getFile() {
return file;
}
public void setFile(String file) {
this.file = file;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public int size() {
if (content != null) {
//粗略计算行数作为item size
return content.length()/100 + 1;
}
return 0;
}
}
package com.taobao.arthas.core.command.model;
/**
* @author gongdewei 2020/4/16
*/
public class ChangeResultVO {
private String name;
private Object beforeValue;
private Object afterValue;
public ChangeResultVO() {
}
public ChangeResultVO(String name, Object beforeValue, Object afterValue) {
this.name = name;
this.beforeValue = beforeValue;
this.afterValue = afterValue;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object getBeforeValue() {
return beforeValue;
}
public void setBeforeValue(Object beforeValue) {
this.beforeValue = beforeValue;
}
public Object getAfterValue() {
return afterValue;
}
public void setAfterValue(Object afterValue) {
this.afterValue = afterValue;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment