cdc.perfs.instrument.asm.AddProbeAdapter Maven / Gradle / Ivy
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);
}
}
}