Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class ArthasMcpJavaSdkIT {
"ognl",
"options",
"perfcounter",
"profiler",
"redefine",
"retransform",
"sc",
Expand All @@ -81,6 +82,7 @@ class ArthasMcpJavaSdkIT {
"tt",
"vmoption",
"vmtool",
"viewfile",
"watch"
);

Expand Down Expand Up @@ -312,6 +314,23 @@ private static Map<String, Object> createArgumentsForTool(String toolName, Envir
return args;
}

if ("profiler".equals(toolName)) {
args.put("action", "actions");
return args;
}

if ("viewfile".equals(toolName)) {
Path outputDir = env.tempHome.resolve("arthas-output");
Files.createDirectories(outputDir);
Path viewFile = outputDir.resolve("viewfile-test.txt");
Files.write(viewFile, "hello viewfile\n".getBytes(StandardCharsets.UTF_8));

args.put("path", "viewfile-test.txt");
args.put("offset", 0L);
args.put("maxBytes", 1024);
return args;
}

if ("dashboard".equals(toolName)) {
args.put("intervalMs", 200);
args.put("numberOfExecutions", 1);
Expand Down
39 changes: 34 additions & 5 deletions core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ public class Enhancer implements ClassFileTransformer {
private final Matcher classNameMatcher;
private final Matcher classNameExcludeMatcher;
private final Matcher methodNameMatcher;
/**
* 指定增强的 classloader hash,如果为空则不限制。
*/
private final String targetClassLoaderHash;
private final EnhancerAffect affect;
private Set<Class<?>> matchingClasses = null;
private boolean isLazy = false;
Expand All @@ -99,7 +103,7 @@ public class Enhancer implements ClassFileTransformer {
public Enhancer(AdviceListener listener, boolean isTracing, boolean skipJDKTrace, Matcher classNameMatcher,
Matcher classNameExcludeMatcher,
Matcher methodNameMatcher) {
this(listener, isTracing, skipJDKTrace, classNameMatcher, classNameExcludeMatcher, methodNameMatcher, false);
this(listener, isTracing, skipJDKTrace, classNameMatcher, classNameExcludeMatcher, methodNameMatcher, false, null);
}

/**
Expand All @@ -114,12 +118,19 @@ public Enhancer(AdviceListener listener, boolean isTracing, boolean skipJDKTrace
public Enhancer(AdviceListener listener, boolean isTracing, boolean skipJDKTrace, Matcher classNameMatcher,
Matcher classNameExcludeMatcher,
Matcher methodNameMatcher, boolean isLazy) {
this(listener, isTracing, skipJDKTrace, classNameMatcher, classNameExcludeMatcher, methodNameMatcher, isLazy, null);
}

public Enhancer(AdviceListener listener, boolean isTracing, boolean skipJDKTrace, Matcher classNameMatcher,
Matcher classNameExcludeMatcher,
Matcher methodNameMatcher, boolean isLazy, String targetClassLoaderHash) {
this.listener = listener;
this.isTracing = isTracing;
this.skipJDKTrace = skipJDKTrace;
this.classNameMatcher = classNameMatcher;
this.classNameExcludeMatcher = classNameExcludeMatcher;
this.methodNameMatcher = methodNameMatcher;
this.targetClassLoaderHash = targetClassLoaderHash;
this.affect = new EnhancerAffect();
affect.setListenerId(listener.id());
this.isLazy = isLazy;
Expand Down Expand Up @@ -154,6 +165,10 @@ public byte[] transform(final ClassLoader inClassLoader, String className, Class
if (classNameExcludeMatcher != null && classNameExcludeMatcher.matching(classNameDot)) {
return null;
}
// 检查 classloader 是否匹配(指定了 targetClassLoaderHash 时生效)
if (!isTargetClassLoader(inClassLoader)) {
return null;
}
// 检查是否是 arthas 自身的类
if (inClassLoader != null && isEquals(inClassLoader, selfClassLoader)) {
return null;
Expand Down Expand Up @@ -369,6 +384,9 @@ private List<Pair<Class<?>, String>> filter(Set<Class<?>> classes) {
boolean removeFlag = false;
if (null == clazz) {
removeFlag = true;
} else if (!isTargetClassLoader(clazz.getClassLoader())) {
filteredClasses.add(new Pair<Class<?>, String>(clazz, "classloader is not matched"));
removeFlag = true;
} else if (isSelf(clazz)) {
filteredClasses.add(new Pair<Class<?>, String>(clazz, "class loaded by arthas itself"));
removeFlag = true;
Expand Down Expand Up @@ -457,10 +475,6 @@ public synchronized EnhancerAffect enhance(final Instrumentation inst, int maxNu
? SearchUtils.searchClass(inst, classNameMatcher)
: SearchUtils.searchSubClass(inst, SearchUtils.searchClass(inst, classNameMatcher));

if (matchingClasses.size() > maxNumOfMatchedClass) {
affect.setOverLimitMsg("The number of matched classes is " +matchingClasses.size()+ ", greater than the limit value " + maxNumOfMatchedClass + ". Try to change the limit with option '-m <arg>'.");
return affect;
}
// 过滤掉无法被增强的类
List<Pair<Class<?>, String>> filtedList = filter(matchingClasses);
if (!filtedList.isEmpty()) {
Expand All @@ -469,6 +483,11 @@ public synchronized EnhancerAffect enhance(final Instrumentation inst, int maxNu
}
}

if (matchingClasses.size() > maxNumOfMatchedClass) {
affect.setOverLimitMsg("The number of matched classes is " +matchingClasses.size()+ ", greater than the limit value " + maxNumOfMatchedClass + ". Try to change the limit with option '-m <arg>'.");
return affect;
}

logger.info("enhance matched classes: {}", matchingClasses);

affect.setTransformer(this);
Expand Down Expand Up @@ -518,6 +537,16 @@ public synchronized EnhancerAffect enhance(final Instrumentation inst, int maxNu
return affect;
}

private boolean isTargetClassLoader(ClassLoader inClassLoader) {
if (targetClassLoaderHash == null || targetClassLoaderHash.isEmpty()) {
return true;
}
if (inClassLoader == null) {
return false;
}
return Integer.toHexString(inClassLoader.hashCode()).equalsIgnoreCase(targetClassLoaderHash);
}

/**
* 重置指定的Class
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,27 @@ public abstract class EnhancerCommand extends AnnotatedCommand {

protected boolean lazy = false;

/**
* 指定 classloader hash,只增强该 classloader 加载的类。
*/
protected String hashCode;

@Option(longName = "exclude-class-pattern")
@Description("exclude class name pattern, use either '.' or '/' as separator")
public void setExcludeClassPattern(String excludeClassPattern) {
this.excludeClassPattern = excludeClassPattern;
}

@Option(longName = "classloader")
@Description("The hash code of the special class's classLoader")
public void setHashCode(String hashCode) {
this.hashCode = hashCode;
}

public String getHashCode() {
return hashCode;
}

@Option(longName = "listenerId")
@Description("The special listenerId")
public void setListenerId(long listenerId) {
Expand Down Expand Up @@ -195,7 +210,8 @@ protected void enhance(CommandProcess process) {
skipJDKTrace = ((AbstractTraceAdviceListener) listener).getCommand().isSkipJDKTrace();
}

Enhancer enhancer = new Enhancer(listener, listener instanceof InvokeTraceable, skipJDKTrace, getClassNameMatcher(), getClassNameExcludeMatcher(), getMethodNameMatcher(), this.lazy);
Enhancer enhancer = new Enhancer(listener, listener instanceof InvokeTraceable, skipJDKTrace,
getClassNameMatcher(), getClassNameExcludeMatcher(), getMethodNameMatcher(), this.lazy, this.hashCode);
// 注册通知监听器
process.register(listener, enhancer);
effect = enhancer.enhance(inst, this.maxNumOfMatchedClass);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ public void setNumberOfLimit(int numberOfLimit) {
this.numberOfLimit = numberOfLimit;
}

@Override
@Option(shortName = "c", longName = "classloader")
@Description("The hash code of the special class's classLoader")
public void setHashCode(String hashCode) {
super.setHashCode(hashCode);
}

public String getClassPattern() {
return classPattern;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ public void setSizeLimit(Integer sizeLimit) {
this.sizeLimit = sizeLimit;
}

@Override
@Option(shortName = "c", longName = "classloader")
@Description("The hash code of the special class's classLoader")
public void setHashCode(String hashCode) {
super.setHashCode(hashCode);
}

@Option(shortName = "w", longName = "watch-express")
@Description(value = "watch the time fragment by ognl express.\n" + Constants.EXPRESS_EXAMPLES)
public void setWatchExpress(String watchExpress) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ public void setSkipJDKTrace(boolean skipJDKTrace) {
this.skipJDKTrace = skipJDKTrace;
}

@Override
@Option(shortName = "c", longName = "classloader")
@Description("The hash code of the special class's classLoader")
public void setHashCode(String hashCode) {
super.setHashCode(hashCode);
}

public String getClassPattern() {
return classPattern;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ public void setNumberOfLimit(int numberOfLimit) {
this.numberOfLimit = numberOfLimit;
}

@Override
@Option(shortName = "c", longName = "classloader")
@Description("The hash code of the special class's classLoader")
public void setHashCode(String hashCode) {
super.setHashCode(hashCode);
}

public String getClassPattern() {
return classPattern;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.arthas.SpyAPI;
import java.lang.instrument.Instrumentation;
import java.net.URL;
import java.net.URLClassLoader;

import org.assertj.core.api.Assertions;
import org.junit.Test;
Expand All @@ -14,6 +16,7 @@
import com.alibaba.deps.org.objectweb.asm.tree.MethodNode;
import com.taobao.arthas.core.bytecode.TestHelper;
import com.taobao.arthas.core.server.ArthasBootstrap;
import com.taobao.arthas.core.util.ClassLoaderUtils;
import com.taobao.arthas.core.util.matcher.EqualsMatcher;

import demo.MathGame;
Expand Down Expand Up @@ -92,4 +95,43 @@ public void test() throws Throwable {
System.err.println(string);
}

@Test
public void testEnhanceWithClassLoaderHash() throws Throwable {
Instrumentation instrumentation = ByteBuddyAgent.install();
TestHelper.appendSpyJar(instrumentation);
ArthasBootstrap.getInstance(instrumentation, "ip=127.0.0.1");

URL codeSource = MathGame.class.getProtectionDomain().getCodeSource().getLocation();
URLClassLoader anotherClassLoader = new URLClassLoader(new URL[] { codeSource }, null);
try {
Class<?> anotherMathGame = Class.forName(MathGame.class.getName(), true, anotherClassLoader);
Assertions.assertThat(anotherMathGame.getClassLoader()).isNotSameAs(MathGame.class.getClassLoader());

AdviceListener listener = Mockito.mock(AdviceListener.class);
EqualsMatcher<String> methodNameMatcher = new EqualsMatcher<String>("print");
EqualsMatcher<String> classNameMatcher = new EqualsMatcher<String>(MathGame.class.getName());

// Enhancer 会过滤与自身 ClassLoader 相同的类(认为是 Arthas 自身加载的类)。
// 这里用另一个 ClassLoader 加载一份同名类,并用 classloader hash 精确指定只增强这一份。
String targetClassLoaderHash = Integer.toHexString(anotherClassLoader.hashCode());
Enhancer enhancer = new Enhancer(listener, false, false, classNameMatcher, null, methodNameMatcher, false,
targetClassLoaderHash);

com.taobao.arthas.core.util.affect.EnhancerAffect affect = enhancer.enhance(instrumentation, 50);

String expectedMethodPrefix = ClassLoaderUtils.classLoaderHash(anotherClassLoader) + "|"
+ MathGame.class.getName() + "#print|";
String nonTargetMethodPrefix = ClassLoaderUtils.classLoaderHash(MathGame.class.getClassLoader()) + "|" + MathGame.class.getName()
+ "#print|";

Assertions.assertThat(affect.cCnt()).isEqualTo(1);
Assertions.assertThat(affect.mCnt()).isEqualTo(1);
Assertions.assertThat(affect.getMethods()).hasSize(1);
Assertions.assertThat(affect.getMethods()).allMatch(m -> m.startsWith(expectedMethodPrefix));
Assertions.assertThat(affect.getMethods()).noneMatch(m -> m.startsWith(nonTargetMethodPrefix));
} finally {
anotherClassLoader.close();
}
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.taobao.arthas.core.mcp.tool.function.basic1000;

import com.fasterxml.jackson.core.type.TypeReference;
import com.taobao.arthas.common.JavaVersionUtils;
import com.taobao.arthas.mcp.server.tool.ToolContext;
import com.taobao.arthas.mcp.server.util.JsonParser;
import org.junit.*;
Expand All @@ -16,6 +17,10 @@
import java.util.LinkedHashMap;
import java.util.Map;

/**
* ViewFileTool 单元测试
* 仅在 JDK 8 下运行,因为测试中使用反射修改环境变量的方式在高版本 JDK 中不可用
*/
public class ViewFileToolTest {

@Rule
Expand All @@ -27,6 +32,10 @@ public class ViewFileToolTest {

@Before
public void setUp() {
// 仅在 JDK 8 下运行测试
Assume.assumeTrue("此测试仅在 JDK 8 下运行", JavaVersionUtils.isJava8());
// Windows 下环境变量的内部实现不同,跳过测试
Assume.assumeFalse("此测试在 Windows 下不运行", System.getProperty("os.name").toLowerCase().contains("win"));
clearEnv(ViewFileTool.ALLOWED_DIRS_ENV);
}

Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,13 @@
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.11</version>
<version>1.18.3</version>
</dependency>

<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.14.11</version>
<version>1.18.3</version>
</dependency>

<dependency>
Expand Down
10 changes: 10 additions & 0 deletions site/docs/doc/monitor.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
| _condition-express_ | 条件表达式 |
| [E] | 开启正则表达式匹配,默认为通配符匹配 |
| `[c:]` | 统计周期,默认值为 60 秒 |
| `--classloader` | 指定 classloader hash,只增强该 classloader 加载的类 |
| [b] | 在**方法调用之前**计算 condition-express |
| `[m <arg>]` | 指定 Class 最大匹配数量,默认值为 50。长格式为`[maxMatch <arg>]`。 |

Expand Down Expand Up @@ -87,6 +88,15 @@ Affect(class count:1 , method count:1) cost in 384 ms, listenerId: 6.
2022-12-25 21:12:59 demo.MathGame primeFactors 0 0 0 0.00 0.00%
```

### 指定 ClassLoader 增强

当同名类被多个 classloader 加载时,可以先用 `sc -d` 查看 classloader hash,然后用 `--classloader` 指定增强的 classloader(注意 `-c` 在 monitor 里表示统计周期):

```bash
sc -d com.example.Foo
monitor --classloader 3d4eac69 com.example.Foo bar
```

### 计算条件表达式过滤统计结果(方法执行完毕之后)

```bash
Expand Down
10 changes: 10 additions & 0 deletions site/docs/doc/stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
| _condition-express_ | 条件表达式 |
| [E] | 开启正则表达式匹配,默认为通配符匹配 |
| `[n:]` | 执行次数限制 |
| `[c:]` | 指定 classloader hash,只增强该 classloader 加载的类 |
| `[m <arg>]` | 指定 Class 最大匹配数量,默认值为 50。长格式为`[maxMatch <arg>]`。 |

这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写`"{params,returnObj}"`,只要是一个合法的 ognl 表达式,都能被正常支持。
Expand Down Expand Up @@ -57,6 +58,15 @@ ts=2022-12-25 21:07:07;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun
at demo.MathGame.main(MathGame.java:38)
```

### 指定 ClassLoader 增强

当同名类被多个 classloader 加载时,可以先用 `sc -d` 查看 classloader hash,然后用 `-c` 指定增强的 classloader:

```bash
sc -d com.example.Foo
stack -c 3d4eac69 com.example.Foo bar
```

### 据条件表达式来过滤

```bash
Expand Down
Loading
Loading