All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cdc.perfs.instrument.asm.AddProbeAdapter Maven / Gradle / Ivy

There is a newer version: 0.52.0
Show newest version
package cdc.perfs.instrument.asm;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;

import cdc.perfs.api.MeasureLevel;
import cdc.perfs.api.RuntimeProbe;
import cdc.perfs.instrument.Configurator;
import cdc.util.lang.Introspection;
import cdc.util.lang.UnexpectedValueException;

/**
 * Class visitor that can insert probes in the code of a class files.
 * 

* It does the following in a class 'Foo': *

    *
  • Adds a static field:
    *
        private static final Source ${SOURCE} = RuntimeManager.getSource(Foo.class);}
    *
  • In each candidate method 'bar', adds:
    *
        final RuntimeProbe probe = RuntimeManager.createProbe(${SOURCE}, ${MeasureLevel});
     *    probe.start("bar()");
     *    try {
     *       ...
     *    } finally {
     *       probe.stop();
     *    }
    *
* * @author Damien Carbonne */ final class AddProbeAdapter extends ClassVisitor implements Opcodes { private static final Logger LOGGER = LogManager.getLogger(AddProbeAdapter.class); /** API level to use for visitors. */ private static final int ASM_API = ASM8; /** Constructor name; */ private static final String INIT = ""; /** Class initializer name. */ private static final String CLINIT = ""; private static final String VALUE_OF = "valueOf"; private static final String CLASS_MEASURE_LEVEL = "cdc/perfs/api/MeasureLevel"; private static final String CLASS_RUNTIME_MANAGER = "cdc/perfs/api/RuntimeManager"; private static final String CLASS_RUNTIME_PROBE = "cdc/perfs/api/RuntimeProbe"; private static final String CLASS_SOURCE = "cdc/perfs/api/Source"; /** The instrumentation configurator. */ protected final Configurator configurator; private final String originalPackageName; private final String originalSimpleName; private final boolean mustInstrumentClass; /** Class name **/ protected String internalClassName; protected String internalPackageName; protected String internalSimpleName; /** Does the .class file contains an interface ? */ private boolean isInterface; /** Does the .class file contains a class initializer section ? */ private boolean hasClassInit = false; public AddProbeAdapter(ClassVisitor cv, String originalClassName, Configurator configurator) { super(ASM_API, cv); this.originalPackageName = Introspection.getPackagePart(originalClassName); this.originalSimpleName = Introspection.getClassPart(originalClassName); this.configurator = configurator; this.mustInstrumentClass = configurator.mustInstrumentClass(originalPackageName, originalSimpleName); } private static Type[] getArgTypes(String desc) { return Type.getArgumentTypes(desc); } private static String[] getArgClassNames(Type[] types) { final String[] result = new String[types.length]; for (int index = 0; index < types.length; index++) { result[index] = types[index].getClassName(); } return result; } /** * @return The name to use for source name in this class. */ protected String getSourceName() { return configurator.getSourceName(originalPackageName, originalSimpleName); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { LOGGER.debug("visit({}, {})", name, signature); cv.visit(version, access, name, signature, superName, interfaces); internalClassName = name; internalPackageName = Introspection.getPackagePart(name); internalSimpleName = Introspection.getClassPart(name); isInterface = (access & ACC_INTERFACE) != 0; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); LOGGER.debug("visitMethod({}, {})", name, desc); if (!isInterface && mustInstrumentClass) { if (CLINIT.equals(name)) { LOGGER.debug("Instrument class initializer code"); mv = new AddClassInitMethodAdapter(mv, this); hasClassInit = true; } else if (mv != null && !INIT.equals(name)) { final Type[] argTypes = getArgTypes(desc); final String[] argClassNames = getArgClassNames(argTypes); if (configurator.mustInstrumentMethod(originalPackageName, originalSimpleName, name, argClassNames)) { final boolean showArgs = configurator.mustShowMethodArgs(originalPackageName, originalSimpleName, name, argClassNames); mv = new AddProbeMethodAdapter(mv, access, name, desc, this, argTypes, argClassNames, showArgs); } } } return mv; } @Override public void visitEnd() { LOGGER.debug("visitEnd()"); if (!isInterface && mustInstrumentClass) { final FieldVisitor fv = cv.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, getSourceName(), "L" + CLASS_SOURCE + ";", null, null); if (fv != null) { fv.visitEnd(); } if (!hasClassInit) { LOGGER.debug("Create class initializer code"); MethodVisitor mv = super.visitMethod(ACC_STATIC, CLINIT, "()V", null, null); mv = new AddClassInitMethodAdapter(mv, this); mv.visitCode(); mv.visitInsn(RETURN); mv.visitMaxs(2, 0); mv.visitEnd(); } } cv.visitEnd(); } /** * Visitor used to add class initializer code. *

* Should only be used when associated class needs to be instrumented. * * @author Damien Carbonne */ private static class AddClassInitMethodAdapter extends MethodVisitor { private static final Logger LOGGER = LogManager.getLogger(AddProbeAdapter.AddClassInitMethodAdapter.class); private final AddProbeAdapter context; public AddClassInitMethodAdapter(MethodVisitor mv, AddProbeAdapter context) { super(ASM_API, mv); LOGGER.debug("(...)"); this.context = context; } @Override public void visitCode() { // Start visit of code of a method LOGGER.debug("visitCode() {}", context.internalClassName); mv.visitCode(); mv.visitLdcInsn(Type.getType("L" + context.internalClassName + ";")); mv.visitMethodInsn(INVOKESTATIC, CLASS_RUNTIME_MANAGER, "getSource", "(Ljava/lang/Class;)L" + CLASS_SOURCE + ";", false); mv.visitFieldInsn(PUTSTATIC, context.internalClassName, context.getSourceName(), "L" + CLASS_SOURCE + ";"); } } private static class AddProbeMethodAdapter extends AdviceAdapter { private static final Logger LOGGER = LogManager.getLogger(AddProbeMethodAdapter.class); private static final Type TYPE_RUNTIME_PROBE = Type.getType(RuntimeProbe.class); private final AddProbeAdapter context; /** Method name */ private final String methodName; private final Type[] argTypes; private final String[] argClassNames; private final boolean showArgs; /** Try block start label. */ private final Label startFinally = new Label(); private int probeVariable; public AddProbeMethodAdapter(MethodVisitor mv, int access, String name, String desc, AddProbeAdapter context, Type[] argTypes, String[] argClassNames, boolean showArgs) { super(ASM_API, mv, access, name, desc); this.context = context; this.methodName = name; this.argTypes = argTypes; this.argClassNames = argClassNames; this.showArgs = showArgs; } /** * @return The level (as a String) to be used for the visited method. */ private String getMeasureLevel() { final MeasureLevel level = context.configurator.getMethodMeasureLevel(context.internalPackageName, context.internalSimpleName, methodName, argClassNames); return level == null ? null : level.name(); } @Override public void visitCode() { LOGGER.trace("visitCode()"); super.visitCode(); declareAndStartProbe(); mv.visitLabel(startFinally); } @Override protected void onMethodExit(int opcode) { LOGGER.trace("onMethodExit({})", opcode); if (opcode != ATHROW) { onFinally(opcode); } } private void onFinally(int opcode) { LOGGER.trace("onFinally({})", opcode); // probe.stop() mv.visitVarInsn(ALOAD, probeVariable); mv.visitMethodInsn(INVOKEINTERFACE, CLASS_RUNTIME_PROBE, "stop", "()V", true); } private void declareAndStartProbe() { LOGGER.trace("declareAndStartProbe()"); // final RuntimeProbe probe = RuntimeManager.createProbe(SOURCE, MeasureLevel.DEBUG) // Load 1st arg on operand stack: SOURCE static attribute mv.visitFieldInsn(GETSTATIC, context.internalClassName, context.getSourceName(), "L" + CLASS_SOURCE + ";"); // Load 2nd arg on operand stack: the enum MeasureLevel mv.visitFieldInsn(GETSTATIC, CLASS_MEASURE_LEVEL, getMeasureLevel(), "L" + CLASS_MEASURE_LEVEL + ";"); // Invoke createProbe mv.visitMethodInsn(INVOKESTATIC, CLASS_RUNTIME_MANAGER, "createProbe", "(L" + CLASS_SOURCE + ";L" + CLASS_MEASURE_LEVEL + ";)L" + CLASS_RUNTIME_PROBE + ";", false); // Create a variable to store created probe probeVariable = newLocal(TYPE_RUNTIME_PROBE); // Store created probe mv.visitVarInsn(ASTORE, probeVariable); // probe.start("...()") // Load the probe variable on the operand stack mv.visitVarInsn(ALOAD, probeVariable); // Load details to be passed to start() loadStartDetails(); // Invoke the start method mv.visitMethodInsn(INVOKEINTERFACE, CLASS_RUNTIME_PROBE, "start", "(Ljava/lang/String;)V", true); } private void loadStartDetails() { LOGGER.trace("loadStartDetails()"); // Load format first argument (method name) on the operand stack mv.visitLdcInsn(methodName); // TODO use variants of format for small numbers of arguments // Load format second argument // Create the appropriate array to store method arguments pushSmallInt(argClassNames.length); mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); for (int index = 0; index < argClassNames.length; index++) { // Duplicate and push the array reference mv.visitInsn(DUP); // Push the index to use for array store pushSmallInt(index); // Push the arg if (showArgs) { loadCallerArg(index); } else { mv.visitLdcInsn("?"); } // Store arg in array[index] mv.visitInsn(AASTORE); } // Call format mv.visitMethodInsn(INVOKESTATIC, CLASS_RUNTIME_MANAGER, "format", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", false); } private void loadCallerArg(int index) { LOGGER.trace("loadCallerArg({}): {}", index, argTypes[index]); // If method is static, offset is 0, otherwise is is 1 final int offset = (methodAccess & Opcodes.ACC_STATIC) == 0 ? 1 : 0; final Type type = argTypes[index]; final int sort = type.getSort(); LOGGER.trace("type:{}, sort: {}, offset: {}", type, sort, offset); // Primitive arguments must be boxed to their corresponding Object class if (sort == Type.BOOLEAN) { mv.visitVarInsn(ILOAD, index + offset); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", VALUE_OF, "(Z)Ljava/lang/Boolean;", false); } else if (sort == Type.CHAR) { mv.visitVarInsn(ILOAD, index + offset); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", VALUE_OF, "(C)Ljava/lang/Character;", false); } else if (sort == Type.BYTE) { mv.visitVarInsn(ILOAD, index + offset); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", VALUE_OF, "(B)Ljava/lang/Byte;", false); } else if (sort == Type.SHORT) { mv.visitVarInsn(ILOAD, index + offset); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", VALUE_OF, "(S)Ljava/lang/Short;", false); } else if (sort == Type.INT) { mv.visitVarInsn(ILOAD, index + offset); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", VALUE_OF, "(I)Ljava/lang/Integer;", false); } else if (sort == Type.LONG) { mv.visitVarInsn(LLOAD, index + offset); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", VALUE_OF, "(J)Ljava/lang/Long;", false); } else if (sort == Type.FLOAT) { mv.visitVarInsn(FLOAD, index + offset); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", VALUE_OF, "(F)Ljava/lang/Float;", false); } else if (sort == Type.DOUBLE) { mv.visitVarInsn(DLOAD, index + offset); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", VALUE_OF, "(D)Ljava/lang/Double;", false); } else { // Load the method argument (Object, Array, ...) on the operand stack mv.visitVarInsn(ALOAD, index + offset); } } private void pushSmallInt(int value) { LOGGER.trace("pushSmallInt({})", value); if (value >= 0) { if (value <= 5) { if (value == 0) { mv.visitInsn(ICONST_0); } else if (value == 1) { mv.visitInsn(ICONST_1); } else if (value == 2) { mv.visitInsn(ICONST_2); } else if (value == 3) { mv.visitInsn(ICONST_3); } else if (value == 4) { mv.visitInsn(ICONST_4); } else { mv.visitInsn(ICONST_5); } } else if (value <= 127) { mv.visitIntInsn(BIPUSH, value); } else { throw new UnexpectedValueException(); } } else { throw new UnexpectedValueException(); } } @Override public void visitMaxs(int maxStack, int maxLocals) { LOGGER.trace("visitMaxs({}, {})", maxStack, maxLocals); final Label endFinally = new Label(); mv.visitTryCatchBlock(startFinally, endFinally, endFinally, null); mv.visitLabel(endFinally); onFinally(ATHROW); // mv.visitVarInsn(ALOAD, 3); TODO ??? mv.visitInsn(ATHROW); mv.visitMaxs(maxStack, maxLocals); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy