【字节码】Java agent+ASM实战--监控所有方法执行时间

x33g5p2x  于2022-06-20 转载在 Java  
字(8.6k)|赞(0)|评价(0)|浏览(448)

1.概述

在上一篇文章:【字节码】Java Instrumentation 简介 以及 ASM 组合案例 因为这篇文章我一直找不到原因,然后找到了这篇文章。

转载:JVM插码之五:Java agent+ASM实战–监控所有方法执行时间

本文建立在对instrumentation和agent有初步的了解的前提下阅读,关于这2个类的讲解在其它文章中。

这是一个maven项目,pom中需要的配置,lib中有asm的jar包

maven如下

  1. <groupId>org.example</groupId>
  2. <artifactId>javaagent-apiv2</artifactId>
  3. <version>1.0-SNAPSHOT</version>
  4. <packaging>jar</packaging>
  5. <properties>
  6. <maven.compiler.source>8</maven.compiler.source>
  7. <maven.compiler.target>8</maven.compiler.target>
  8. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  9. <asm.version>9.0</asm.version>
  10. </properties>
  11. <dependencies>
  12. <dependency>
  13. <groupId>org.ow2.asm</groupId>
  14. <artifactId>asm-analysis</artifactId>
  15. <version>${asm.version}</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.ow2.asm</groupId>
  19. <artifactId>asm-commons</artifactId>
  20. <version>${asm.version}</version>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.ow2.asm</groupId>
  24. <artifactId>asm-tree</artifactId>
  25. <version>${asm.version}</version>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.ow2.asm</groupId>
  29. <artifactId>asm-util</artifactId>
  30. <version>${asm.version}</version>
  31. </dependency>
  32. <dependency>
  33. <groupId>javassist</groupId>
  34. <artifactId>javassist</artifactId>
  35. <version>3.12.1.GA</version>
  36. </dependency>
  37. <!-- https://mvnrepository.com/artifact/cglib/cglib -->
  38. <dependency>
  39. <groupId>cglib</groupId>
  40. <artifactId>cglib</artifactId>
  41. <version>3.2.5</version>
  42. </dependency>
  43. <!-- https://mvnrepository.com/artifact/oro/oro -->
  44. <dependency>
  45. <groupId>oro</groupId>
  46. <artifactId>oro</artifactId>
  47. <version>2.0.8</version>
  48. </dependency>
  49. <dependency>
  50. <groupId>junit</groupId>
  51. <artifactId>junit</artifactId>
  52. <version>3.8.1</version>
  53. <scope>test</scope>
  54. </dependency>
  55. </dependencies>
  56. <build>
  57. <plugins>
  58. <plugin>
  59. <groupId>org.apache.maven.plugins</groupId>
  60. <artifactId>maven-compiler-plugin</artifactId>
  61. <configuration>
  62. <source>1.8</source>
  63. <target>1.8</target>
  64. <encoding>utf-8</encoding>
  65. </configuration>
  66. </plugin>
  67. <plugin>
  68. <groupId>org.apache.maven.plugins</groupId>
  69. <artifactId>maven-shade-plugin</artifactId>
  70. <version>3.0.0</version>
  71. <executions>
  72. <execution>
  73. <phase>package</phase>
  74. <goals>
  75. <goal>shade</goal>
  76. </goals>
  77. <configuration>
  78. <transformers>
  79. <transformer
  80. implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
  81. <manifestEntries>
  82. <!-- <Premain-Class>com.dxz.chama.javaagent.patter.TimeMonitorPatterAgent</Premain-Class> -->
  83. <!-- <Premain-Class>com.dxz.chama.javaagent.StatAgent</Premain-Class> -->
  84. <Premain-Class>com.javaagent.api.UdAgent</Premain-Class>
  85. </manifestEntries>
  86. </transformer>
  87. </transformers>
  88. </configuration>
  89. </execution>
  90. </executions>
  91. </plugin>
  92. </plugins>
  93. </build>
  94. </project>

agent类,只有一个方法,就是把自定义的类修改器添加到instrumentation中。

  1. public class UdAgent {
  2. public static void premain(String agentArgs, Instrumentation instrumentation){
  3. instrumentation.addTransformer(new LogTransformer());
  4. }
  5. }

类转换器实现:

  1. import java.io.IOException;
  2. import java.lang.instrument.ClassFileTransformer;
  3. import java.lang.instrument.IllegalClassFormatException;
  4. import java.security.ProtectionDomain;
  5. import org.objectweb.asm.ClassReader;
  6. import org.objectweb.asm.ClassWriter;
  7. public class LogTransformer implements ClassFileTransformer {
  8. @Override
  9. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
  10. ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  11. try {
  12. ClassReader cr = new ClassReader(className);
  13. ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
  14. TimeCountAdpter timeCountAdpter = new TimeCountAdpter(cw);
  15. cr.accept(timeCountAdpter, ClassReader.EXPAND_FRAMES);
  16. return cw.toByteArray();
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. }
  20. return null;
  21. }
  22. }

实际修改字节码的方法,这里给每个类添加了一个字段UDASMCN,用于记录当前类的名字(方便打印信息)。同时记录每个方法的名字,以及执行时间。

  1. import org.objectweb.asm.ClassVisitor;
  2. import org.objectweb.asm.FieldVisitor;
  3. import org.objectweb.asm.MethodVisitor;
  4. import org.objectweb.asm.Opcodes;
  5. import org.objectweb.asm.Type;
  6. import org.objectweb.asm.commons.AnalyzerAdapter;
  7. import org.objectweb.asm.commons.LocalVariablesSorter;
  8. public class TimeCountAdpter extends ClassVisitor implements Opcodes {
  9. private String owner;
  10. private boolean isInterface;
  11. private String filedName = "UDASMCN";
  12. private int acc = Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL;
  13. private boolean isPresent = false;
  14. private String methodName;
  15. public TimeCountAdpter(ClassVisitor classVisitor) {
  16. super(ASM6, classVisitor);
  17. }
  18. @Override
  19. public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
  20. super.visit(version, access, name, signature, superName, interfaces);
  21. owner = name;
  22. isInterface = (access & ACC_INTERFACE) != 0;
  23. }
  24. @Override
  25. public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
  26. if (name.equals(filedName)) {
  27. isPresent = true;
  28. }
  29. return super.visitField(access, name, descriptor, signature, value);
  30. }
  31. @Override
  32. public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
  33. String[] exceptions) {
  34. MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
  35. if (!isInterface && mv != null && !name.equals("<init>") && !name.equals("<clinit>")) {
  36. methodName = name;
  37. AddTimerMethodAdapter at = new AddTimerMethodAdapter(mv);
  38. at.aa = new AnalyzerAdapter(owner, access, name, descriptor, at);
  39. at.lvs = new LocalVariablesSorter(access, descriptor, at.aa);
  40. return at.lvs;
  41. }
  42. return mv;
  43. }
  44. public void visitEnd() {
  45. if (!isInterface) {
  46. FieldVisitor fv = cv.visitField(acc, filedName, "Ljava/lang/String;", null, owner);
  47. if (fv != null) {
  48. fv.visitEnd();
  49. }
  50. }
  51. cv.visitEnd();
  52. }
  53. class AddTimerMethodAdapter extends MethodVisitor {
  54. private int time;
  55. private int maxStack;
  56. public LocalVariablesSorter lvs;
  57. public AnalyzerAdapter aa;
  58. public AddTimerMethodAdapter(MethodVisitor methodVisitor) {
  59. super(ASM6, methodVisitor);
  60. }
  61. @Override
  62. public void visitCode() {
  63. mv.visitCode();
  64. mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
  65. time = lvs.newLocal(Type.LONG_TYPE);
  66. mv.visitVarInsn(LSTORE, time);
  67. maxStack = 4;
  68. }
  69. @Override
  70. public void visitInsn(int opcode) {
  71. if (((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) && !isPresent) {
  72. mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
  73. mv.visitVarInsn(LLOAD, time);
  74. mv.visitInsn(LSUB);
  75. mv.visitVarInsn(LSTORE, time);
  76. mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
  77. mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
  78. mv.visitInsn(DUP);
  79. mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
  80. mv.visitFieldInsn(GETSTATIC, owner, filedName, "Ljava/lang/String;");
  81. mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
  82. "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
  83. mv.visitLdcInsn(" " + methodName + ":");
  84. mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
  85. "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
  86. mv.visitVarInsn(LLOAD, time);
  87. mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;",
  88. false);
  89. mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
  90. mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
  91. maxStack = Math.max(aa.stack.size() + 4, maxStack);
  92. }
  93. mv.visitInsn(opcode);
  94. }
  95. @Override
  96. public void visitMaxs(int maxStack, int maxLocals) {
  97. super.visitMaxs(Math.max(maxStack, this.maxStack), maxLocals);
  98. }
  99. }
  100. }

打包成jar包后,在另一个程序启动时调用,启动参数如下:

  1. lcc@lcc javaagent-apiv2$ java -javaagent:./target/javaagent-apiv2-1.0-SNAPSHOT.jar
  2. java/lang/Shutdown runHooks:3294
  3. java/lang/Shutdown sequence:43843
  4. java/lang/Shutdown shutdown:56371

执行效果:

时间单位是纳秒,可以看到每个方法执行完时,都会打印这个方法 的执行时间,以com/Main main:11457636为例,说明类com/Main的main方法执行力11毫秒。

相关文章

最新文章

更多