字节码JavaAgent的全链路监控篇二,通过字节码增加监控执行 耗时

Posted 九师兄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字节码JavaAgent的全链路监控篇二,通过字节码增加监控执行 耗时相关的知识,希望对你有一定的参考价值。

1.概述

上一篇文章:【字节码】javaagent 入门 案例 最简单的案例

转载:https://github.com/fuzhengwei/itstack-demo-bytecode

通过上一章节的介绍,我们已经知道通过配置-javaagent:文件.jar后,在java程序启动时候会执行premain方法。接下来我们使用javassist字节码增强的方式,来监控方法程序的执行耗时

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

关于java字节码的处理,目前有很多工具,如bcel,asm。不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用javassist。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

2.代码示例

代码结构如下


maven设置如下


    <dependencies>
        <dependency>
            <groupId>javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.12.1.GA</version>
        </dependency>
        <dependency>
            <groupId>javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.12.1.GA</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <addClasspath>true</addClasspath>
                            <Premain-Class>com.javaagent.MyAgent</Premain-Class>
                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>


我们的agent类如下

package com.javaagent;

import java.lang.instrument.Instrumentation;

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

package com.javaagent;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;

import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
import java.util.HashSet;
import java.util.Set;

public class MyMonitorTransformer implements ClassFileTransformer 

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

    static 
        classNameSet.add("com.javaagent.MyAgentTest");
    

    @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);
    



我们的测试类如下

package com.javaagent;

import org.junit.Test;

import static org.junit.Assert.*;

public class MyAgentTest 


    public static void main(String[] args) throws Exception 
        MyAgentTest apiTest = new MyAgentTest();
        apiTest.echoHi();

        /****************************************************
         * 测试结果
         * this is my agent:testargs
         * transform: [org.itstack.demo.test.ApiTest]
         * hi agent
         * method:[echoHi] cost:[195纳秒]
         ****************************************************/
    

    private void echoHi() throws Exception 
        System.out.println("hi agent");
    

然后我们执行打包命令

lcc@lcc javaagent-demo2$ mvn clean  package

执行完毕后,添加如下参数

-javaagent:/Users/lcc/IdeaProjects/lcc_work/test-javaagent/javaagent-demo2/target/javaagent-demo2-1.0-SNAPSHOT.jar=testargs

然后运行com.javaagent.MyAgentTest结果如下

this is my agent:testargs
transform: [com.javaagent.MyAgentTest]
hi agent
method:[echoHi] cost:[83220ns]

以上是关于字节码JavaAgent的全链路监控篇二,通过字节码增加监控执行 耗时的主要内容,如果未能解决你的问题,请参考以下文章

字节码基于JavaAgent的全链路监控五- ThreadLocal链路追踪

字节码基于JavaAgent的全链路监控六 基于jvmti定位java异常信 息

字节码基于JavaAgent的全链路监控四-JVM内存与GC信息

字节码JavaAgent ByteBuddy操作监控方法 字节码

字节码javaagent 入门 案例 最简单的案例

基线监控:基于依赖关系的全链路智能监控报警