天天看點

基于JavaAgent的全鍊路監控二《通過位元組碼增加監控執行耗時》

案例簡述

通過上一章節的介紹《嗨!JavaAgent》,我們已經知道通過配置-javaagent:檔案.jar後,在java程式啟動時候會執行premain方法。接下來我們使用javassist位元組碼增強的方式,來監控方法程式的執行耗時。

Javassist是一個開源的分析、編輯和建立Java位元組碼的類庫。是由東京工業大學的數學和計算機科學系的 Shigeru Chiba (千葉 滋)所建立的。它已加入了開放源代碼JBoss應用伺服器項目,通過使用Javassist對位元組碼操作為JBoss實作動态"AOP"架構。
關于java位元組碼的處理,目前有很多工具,如bcel,asm。不過這些都需要直接跟虛拟機指令打交道。如果你不想了解虛拟機指令,可以采用javassist。javassist是jboss的一個子項目,其主要的優點,在于簡單,而且快速。直接使用java編碼的形式,而不需要了解虛拟機指令,就能動态改變類的結構,或者動态生成類。

環境準備

1、IntelliJ IDEA Community Edition

2、jdk1.8.0_45 64位

配置資訊(路徑相關修改為自己的)

1、配置位置:Run/Debug Configurations -> VM options

2、配置内容:-javaagent:E:\itstack\GIT\itstack.org\itstack-demo-agent\itstack-demo-agent-02\target\itstack-demo-agent-02-1.0.0-SNAPSHOT.jar=testargs

代碼示例

itstack-demo-agent-02
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── org.itstack.demo.agent
    │   │       ├── MyAgent.java
    │   │	    └── MyMonitorTransformer.java
    │	└── resources
    │       └── META-INF
    │           └── MANIFEST.MF 	
    └── test
         └── java
             └── org.itstack.demo.test
                 └── ApiTest.java
      
pom.xml (引入javassist并打入到Agent包中)
<properties>
        <!-- Build args -->
	<argline>-Xms512m -Xmx512m</argline>
	<skip_maven_deploy>false</skip_maven_deploy>
	<updateReleaseInfo>true</updateReleaseInfo>
	<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
	<maven.test.skip>true</maven.test.skip>
	<!-- 自定義MANIFEST.MF -->
	<maven.configuration.manifestFile>src/main/resources/META-INF/MANIFEST.MF</maven.configuration.manifestFile>
</properties>

<dependencies>
	<dependency>
		<groupId>javassist</groupId>
		<artifactId>javassist</artifactId>
		<version>3.12.1.GA</version>
		<type>jar</type>
	</dependency>
</dependencies>

<!-- 将javassist包打包到Agent中 -->
<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-shade-plugin</artifactId>
	<executions>
		<execution>
			<phase>package</phase>
			<goals>
				<goal>shade</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<artifactSet>
			<includes>
				<include>javassist:javassist:jar:</include>
			</includes>
		</artifactSet>
	</configuration>
</plugin>            
      
MyAgent.java
/**
 * 部落格:http://itstack.org
 * 論壇:http://bugstack.cn
 * 公衆号:bugstack蟲洞棧  {擷取學習源碼}
 * create by fuzhengwei on 2019
 */
public class MyAgent {

    //JVM 首先嘗試在代理類上調用以下方法
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("this is my agent:" + agentArgs);
        MyMonitorTransformer monitor = new MyMonitorTransformer();
        inst.addTransformer(monitor);
    }

    //如果代理類沒有實作上面的方法,那麼 JVM 将嘗試調用該方法
    public static void premain(String agentArgs) {
    }

}
      
MyMonitorTransformer.java
/**
 * 部落格:http://itstack.org
 * 論壇:http://bugstack.cn
 * 公衆号:bugstack蟲洞棧  {擷取學習源碼}
 * create by fuzhengwei on 2019
 */
public class MyMonitorTransformer implements ClassFileTransformer {

    private static final Set<String> classNameSet = new HashSet<>();

    static {
        classNameSet.add("org.itstack.demo.test.ApiTest");
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        try {
            String currentClassName = className.replaceAll("/", ".");
            if (!classNameSet.contains(currentClassName)) { // 提升classNameSet中含有的類
                return null;
            }
            System.out.println("transform: [" + currentClassName + "]");

            CtClass ctClass = ClassPool.getDefault().get(currentClassName);
            CtBehavior[] methods = ctClass.getDeclaredBehaviors();
            for (CtBehavior method : methods) {
                enhanceMethod(method);
            }
            return ctClass.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;

    }


    private void enhanceMethod(CtBehavior method) throws Exception {
        if (method.isEmpty()) {
            return;
        }
        String methodName = method.getName();
        if ("main".equalsIgnoreCase(methodName)) {
            return;
        }

        final StringBuilder source = new StringBuilder();
        // 前置增強: 打入時間戳
        // 保留原有的代碼處理邏輯
        source.append("{")
                .append("long start = System.nanoTime();\n") //前置增強: 打入時間戳
                .append("$_ = $proceed($$);\n")              //調用原有代碼,類似于method();($$)表示所有的參數
                .append("System.out.print(\"method:[")
                .append(methodName).append("]\");").append("\n")
                .append("System.out.println(\" cost:[\" +(System.nanoTime() - start)+ \"ns]\");") // 後置增強,計算輸出方法執行耗時
                .append("}");

        ExprEditor editor = new ExprEditor() {
            @Override
            public void edit(MethodCall methodCall) throws CannotCompileException {
                methodCall.replace(source.toString());
            }
        };
        method.instrument(editor);
    }

}
      
MANIFEST.MF
Manifest-Version: 1.0
Premain-Class: org.itstack.demo.agent.MyAgent
Can-Redefine-Classes: true
      
ApiTest.java
/**
 * 部落格:http://itstack.org
 * 論壇:http://bugstack.cn
 * 公衆号:bugstack蟲洞棧  {擷取學習源碼}
 * create by fuzhengwei on 2019
 *
 * VM options:
 * -javaagent:E:\itstack\GIT\itstack.org\itstack-demo-agent\itstack-demo-agent-02\target\itstack-demo-agent-02-1.0.0-SNAPSHOT.jar=testargs
 *
 */
public class ApiTest {

    public static void main(String[] args) {
        ApiTest apiTest = new ApiTest();
        apiTest.echoHi();
    }

    private void echoHi(){
        System.out.println("hi agent");
    }

}
      
this is my agent:testargs
transform: [org.itstack.demo.test.ApiTest]
hi agent
method:[echoHi] cost:[294845ns]