Javassist埋点做性能监控

时间:2024-04-01 08:58:40

埋点实现在方法前后动态插入代码,获取方法的执行时间。

常见的方法有以下3钟:

1 硬编码  

2 spirng aop 动态代理

3  动态插入字节码

其中 1 和 2 系统代码侵入性大,方法3不用更改系统代码。

 javaAgent技术

JavaAgent是从JDK1.5及以后引入的,在1.5之前无法使用,也可以叫做java代理。利用 java代理,即 java.lang.instrument 做动态 Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能从本地代码中解放出来,使之可以用 Java 代码的方式解决问题。

使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。在 Java SE 6 里面,instrumentation 包被赋予了更强大的功能:启动后的 instrument、本地代码(native code)instrument,以及动态改变 classpath 等等。这些改变,意味着 Java 具有了更强的动态控制、解释能力,它使得 Java 语言变得更加灵活多变。Instrumentation 的最大作用,就是类定义动态改变和操作。

开发者可以在一个普通 Java 程序(带有 main 函数的 Java 类)运行时,通过 -javaagent参数指定一个特定的 jar 文件(包含 Instrumentation 代理)来启动 Instrumentation 的代理程序。开发者可以让 Instrumentation 代理在 main 函数运行前执行premain函数。

基本步骤:

1  编写premian函数

2  将监控程序打包jar,META-INF/MAINIFEST.MF 必须包含 Premain-Class

3  使用java -javaagent:jar 文件的位置 [= 传入 premain 的参数 ]运行被监控的程序

新建项目JAgent 

1 增加pom依赖

<dependency>
    <groupId>jboss</groupId>
    <artifactId>javassist</artifactId>
    <version>3.8.0.GA</version>
</dependency>

2 编写permian函数

import javassist.*;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;


public class JAgent {

    public static void main(String[] args) {
        System.out.println("main");
    }

    /**
     * 在这个 premain 函数中,开发者可以进行对类的各种操作。
     * @param agentOps
     *        agentArgs premain 函数得到的程序参数,随同 “– javaagent”一起传入。与 main 函数不同的是,
     *        这个参数是一个字符串而不是一个字符串数组,如果程序参数有多个,程序将自行解析这个字符串。
     * @param inst
     *         是一个 java.lang.instrument.Instrumentation 的实例,
     *         JVM 自动传入。java.lang.instrument.Instrumentation instrument 包中定义的一个接口,
     *         也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和操作等等。
     */
    public static void premain(String agentOps, Instrumentation inst) {

        System.out.println("premain:"+agentOps);

        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

                //判断要埋点的类
                if(!"com/chy/JSercice".equals(className)) {
                    return null;
                }

                try {
                    ClassPool classPool=new ClassPool();
                    classPool.insertClassPath(new LoaderClassPath(loader));
                    CtClass ctClass= classPool.get(className.replace("/","."));
                    CtMethod ctMethod= ctClass.getDeclaredMethod("run");

                    //插入本地变量
                    ctMethod.addLocalVariable("begin",CtClass.longType);
                    ctMethod.addLocalVariable("end",CtClass.longType);

                    ctMethod.insertBefore("begin=System.currentTimeMillis();System.out.println(\"begin=\"+begin);");
                    //前面插入:最后插入的放最上面
                    ctMethod.insertBefore("System.out.println( \"埋点开始-2\" );");
                    ctMethod.insertBefore("System.out.println( \"埋点开始-1\" );");

                    ctMethod.insertAfter("end=System.currentTimeMillis();System.out.println(\"end=\"+end);");
                    ctMethod.insertAfter("System.out.println(\"性能:\"+(end-begin)+\"毫秒\");");

                    //后面插入:最后插入的放最下面
                    ctMethod.insertAfter("System.out.println( \"埋点结束-1\" );");
                    ctMethod.insertAfter("System.out.println( \"埋点结束-2\" );");
                    return ctClass.toBytecode();
                } catch (NotFoundException e) {
                    e.printStackTrace();
                } catch (CannotCompileException e) {
                    e.printStackTrace();
                }
                catch (IOException e){
                    e.printStackTrace();
                }

                return new byte[0];
            }
        });
    }
}

3 打包jar

<build>
         <plugins>

             <!--编译Java源码-->
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>3.3</version>
                 <configuration>
                     <source>1.7</source>
                     <target>1.7</target>
                 </configuration>
             </plugin>

             <!-- 打成jar时,设定manifestEntries的参数 -->
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
                 <version>3.0.2</version>
                 <configuration>
                     <archive>
                         <manifestEntries>
                             <Project-name>${project.name}</Project-name>
                             <Project-version>${project.version}</Project-version>
                             <Premain-Class>com.chy.JAgent</Premain-Class>
                             <Can-Redefine-Classes>false</Can-Redefine-Classes>
                         </manifestEntries>                            
                     </archive>
                     <skip>true</skip>
                 </configuration>
             </plugin>

             <!-- 方法1: 包含所有依赖的jar文件,依赖以class的方式存在 -->
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-shade-plugin</artifactId>
                 <version>1.2.1</version>
                 <configuration>
                 </configuration>
                 <executions>
                     <execution>
                         <phase>package</phase>
                         <goals>
                             <goal>shade</goal>
                         </goals>
                         <configuration>
                             <transformers>
                                 <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                     <mainClass>com.chy.JAgent</mainClass>
                                 </transformer>
                             </transformers>
                         </configuration>
                     </execution>
                 </executions>
             </plugin>             

         </plugins>
</build>
<Premain-Class>com.chy.JAgent</Premain-Class> 指定 premain 函数入口

Javassist埋点做性能监控

确保 META-INF/MAINIFEST.MF 必须包含 Premain-Class

新建项目JAgentTest (埋点项目)

public class App 
{
    public static void main( String[] args )
    {
        System.out.println( "JAgentTest is run" );

        // run中埋点统计运行时间
        new JSercice().run();
    }
}

public class JSercice {

    public void call() {
        String name = "JSercice";
        for (int j = 1; j <= 10000; j++) {
            System.out.println(j);
        }
        System.out.println(name + " is end");
    }

    public void run() {
        System.out.println("JSercice is start");
        call();
    }
}

配置项目 vm 参数 

Javassist埋点做性能监控

-javaagent:G:\java\intellij_idea\IdeaProjects\javaByteCode\JAgent\target\JAgent-1.0-SNAPSHOT.jar=JAgent

运行JAgentTest 打印结果如下

premain:JAgent
JAgentTest is run
埋点开始-1
埋点开始-2
begin=1530337378023

JSercice is start

..........

JSercice is end
end=1530337378024
性能:1毫秒
埋点结束-1
埋点结束-2