美文网首页代码记忆
基于Java Agent的attach方式实现方法耗时监控

基于Java Agent的attach方式实现方法耗时监控

作者: Acamy丶 | 来源:发表于2019-01-26 20:54 被阅读71次

    在上一篇中我们已经介绍了java agent的相关概念和思想,给出了premain方式的实现代码。本篇主要是实现了attach方式,不同之处主要如下:

    1. premain是静态修改,在类加载之前修改; attach是动态修改,在类加载后修改
    2. 要使premain生效重启应用,而attach不重启应用即可修改字节码并让其重新加载

    可以看到attach的方式更加强大,其核心原理首先是找到相关的进程id, 然后根据进程id去动态修改相关字节码,具体的修改方式和premain无差,下面就直接给出详细实现。

    项目结构(此处为了方便把主程序和Agent程序放在一起, 实际生产中肯定是分开的):

    agentdemo
    ├── pom.xml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── hebh
            │           └── demo
            │               ├── agent
            │               │   ├── MyInstrumentationAgent.java
            │               │   └── MyTransformer.java
            │               └── application
            │                   ├── AgentLoader.java
            │                   ├── Launcher.java
            │                   ├── MyApplication.java
            │                   └── Runner.java
            └── resources
                ├── META-INF
                │   └── MANIFEST.MF
                └── log4j2.xml
    

    先看测试结果:

    1. 打成jar包

      mvn clean package

    2. 运行主程序

      java -jar target/myAgent-jar-with-dependencies.jar

    3. 运行agent程序, 注意带上系统的lib目录

      java -Djava.ext.dirs=${JAVA_HOME}/lib -jar target/myAgent-jar-with-dependencies.jar LoadAgent

      如下图所示,可以看到首先找到主程序的进程id为4477,然后再attach上去

    image-20190126202600306

    然后此时再看主程序的运行日志, 可以看到在attach后动态增加的字节码生效了,实现了方法耗时监控:

    image-20190126203002824

    详细代码:

    MANIFEST.MF

    Main-Class: com.hebh.demo.application.Launcher
    Agent-Class: com.hebh.demo.agent.MyInstrumentationAgent
    Can-Redefine-Classes: true
    Can-Retransform-Classes: true
    
    

    pom

    <?xml version="1.0" encoding="UTF-8"?>
    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.hebh</groupId>
        <artifactId>agent-demo</artifactId>
        <packaging>jar</packaging>
        <version>1.0-SNAPSHOT</version>
        <name>A custom project using myfaces</name>
        <url>http://www.myorganization.org</url>
    
        <build>
            <finalName>myAgent</finalName>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <configuration>
                        <archive>
                            <!--避免MANIFEST.MF被覆盖-->
                            <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
                        </archive>
                        <descriptorRefs>
                            <!--打包时加入依赖-->
                            <descriptorRef>jar-with-dependencies</descriptorRef>
                        </descriptorRefs>
                    </configuration>
                    <executions>
                        <execution>
                            <id>make-assembly</id> <!-- this is used for inheritance merges -->
                            <phase>package</phase> <!-- bind to the packaging phase -->
                            <goals>
                                <goal>single</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
        <!-- Project dependencies -->
        <dependencies>
            <dependency>
                <groupId>com.sun</groupId>
                <artifactId>tools</artifactId>
                <version>1.8</version>
                <scope>system</scope>
                <systemPath>${java.home}/../lib/tools.jar</systemPath>
            </dependency>
    
            <!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
            <dependency>
                <groupId>org.javassist</groupId>
                <artifactId>javassist</artifactId>
                <version>3.24.1-GA</version>
            </dependency>
    
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-api</artifactId>
                <version>2.11.1</version>
            </dependency>
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-core</artifactId>
                <version>2.11.1</version>
            </dependency>
        </dependencies>
    </project>
    
    

    主程序和Agent程序的路由:

    public class Launcher {
        public static void main(String[] args) throws Exception {
            if(args != null && args.length > 0 && "LoadAgent".equals(args[0])) {
                new AgentLoader().run();
            }else{
                new MyApplication().run();
            }
        }
    }
    

    主程序部分:

    public class MyApplication {
        private static Logger logger = LogManager.getLogger(MyApplication.class);
    
        public static void run() throws Exception {
            logger.info("[Application] Starting My application");
            Runner runner = new Runner();
            for(;;){
                runner.run();
            }
        }
    }
    
    public class Runner {
        private static final Logger logger = LogManager.getLogger(Runner.class);
        public void run() throws InterruptedException{
            long sleep = (long)(Math.random() * 1000 + 200);
            Thread.sleep(sleep);
            logger.info("run in [{}] millis!", sleep);
        }
    }
    

    Agent部分:

    public class AgentLoader {
        private static Logger logger = LogManager.getLogger(AgentLoader.class);
    
        public static void run() {
            //指定jar路径
            String agentFilePath = "/Users/baohuahe/demos/agentdemo/target/myAgent-jar-with-dependencies.jar";
            
            //需要attach的进程标识
            String applicationName = "myAgent";
    
            //查到需要监控的进程
            Optional<String> jvmProcessOpt = Optional.ofNullable(VirtualMachine.list()
                    .stream()
                    .filter(jvm -> {
                        logger.info("jvm:{}", jvm.displayName());
                        return jvm.displayName().contains(applicationName);
                    })
                    .findFirst().get().id());
    
            if(!jvmProcessOpt.isPresent()) {
                logger.error("Target Application not found");
                return;
            }
            File agentFile = new File(agentFilePath);
            try {
                String jvmPid = jvmProcessOpt.get();
                logger.info("Attaching to target JVM with PID: " + jvmPid);
                VirtualMachine jvm = VirtualMachine.attach(jvmPid);
                jvm.loadAgent(agentFile.getAbsolutePath());
                jvm.detach();
                logger.info("Attached to target JVM and loaded Java agent successfully");
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    }
    
    public class MyInstrumentationAgent {
        private static Logger logger = LogManager.getLogger(MyInstrumentationAgent.class);
    
        public static void agentmain(String agentArgs, Instrumentation inst) {
            logger.info("[Agent] In agentmain method");
    
            //需要监控的类
            String className = "com.hebh.demo.application.Runner";
            transformClass(className, inst);
        }
    
        private static void transformClass(String className, Instrumentation instrumentation) {
            Class<?> targetCls = null;
            ClassLoader targetClassLoader = null;
            // see if we can get the class using forName
            try {
                targetCls = Class.forName(className);
                targetClassLoader = targetCls.getClassLoader();
                transform(targetCls, targetClassLoader, instrumentation);
                return;
            } catch (Exception ex) {
                logger.error("Class [{}] not found with Class.forName");
            }
            // otherwise iterate all loaded classes and find what we want
            for(Class<?> clazz: instrumentation.getAllLoadedClasses()) {
                if(clazz.getName().equals(className)) {
                    targetCls = clazz;
                    targetClassLoader = targetCls.getClassLoader();
                    transform(targetCls, targetClassLoader, instrumentation);
                    return;
                }
            }
            throw new RuntimeException("Failed to find class [" + className + "]");
        }
    
        private static void transform(Class<?> clazz, ClassLoader classLoader, Instrumentation instrumentation) {
            MyTransformer dt = new MyTransformer(clazz.getName(), classLoader);
            instrumentation.addTransformer(dt, true);
            try {
                instrumentation.retransformClasses(clazz);
            } catch (Exception ex) {
                throw new RuntimeException("Transform failed for class: [" + clazz.getName() + "]", ex);
            }
        }
    
    }
    
    public class MyTransformer implements ClassFileTransformer {
        private static Logger logger = LogManager.getLogger(MyTransformer.class);
    
        //需要监控的方法
        private static final String WITHDRAW_MONEY_METHOD = "run";
    
        /** The internal form class name of the class to transform */
        private String targetClassName;
        /** The class loader of the class we want to transform */
        private ClassLoader targetClassLoader;
    
        public MyTransformer(String targetClassName, ClassLoader targetClassLoader) {
            this.targetClassName = targetClassName;
            this.targetClassLoader = targetClassLoader;
        }
    
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            byte[] byteCode = classfileBuffer;
            String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/"); //replace . with /
            if (!className.equals(finalTargetClassName)) {
                return byteCode;
            }
    
            if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
                logger.info("[Agent] Transforming class" + className);
                try {
                    ClassPool cp = ClassPool.getDefault();
                    CtClass cc = cp.get(targetClassName);
                    CtMethod m = cc.getDeclaredMethod(WITHDRAW_MONEY_METHOD);
    
                    // 开始时间
                    m.addLocalVariable("startTime", CtClass.longType);
                    m.insertBefore("startTime = System.currentTimeMillis();");
    
                    StringBuilder endBlock = new StringBuilder();
    
                    // 结束时间
                    m.addLocalVariable("endTime", CtClass.longType);
                    endBlock.append("endTime = System.currentTimeMillis();");
    
                    // 时间差
                    m.addLocalVariable("opTime", CtClass.longType);
                    endBlock.append("opTime = endTime-startTime;");
    
                    // 打印方法耗时
                    endBlock.append("logger.info(\"completed in:\" + opTime + \" millis!\");");
    
                    m.insertAfter(endBlock.toString());
    
                    byteCode = cc.toBytecode();
                    cc.detach();
                } catch (Exception e) {
                    logger.error("Exception", e);
                }
            }
            return byteCode;
        }
    }
    

    相关文章

      网友评论

        本文标题:基于Java Agent的attach方式实现方法耗时监控

        本文链接:https://www.haomeiwen.com/subject/cskwjqtx.html