/**************************************************************************************
* Copyright (c) Jonas Bon�r, Alexandre Vasseur. All rights reserved. *
* http://aspectwerkz.codehaus.org *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the LGPL license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package test.memusage;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import java.net.URLClassLoader;
import java.net.URL;
import java.io.File;
import java.util.List;
import java.util.ArrayList;
import org.codehaus.aspectwerkz.hook.impl.WeavingClassLoader;
import org.apache.bcel.Constants;
import org.apache.bcel.generic.*;
/**
* @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur</a>
* @TODO: Port to javassist (and hack the attribute def)
* <p/>
* XML definition:
* <p/>
* <advice-def name="memUsage" class="test.xmldef.memusage.MyAroundAdvice"/> <aspect name="memUsage"> <pointcut-def
* name="pcHello" type="method" pattern="* test.xmldef.memusage.Hello+.sayHello*(..)"/> <bind-advice pointcut="pcHello">
* <advice-ref name="memUsage"/> </bind-advice> </aspect>
* <p/>
* <p/>
* This test can create as many classes as needed. Classes are dumped in _temp dir. A single test loads classFactor
* classes and creates and invokes instanceFactor instances of each Use CLASS_KSIZE and INSTANCE_KSIZE to adapt the
* object sizes.
* <p/>
* Eg: with 50 class and 10 instance with KSize of 10 for each this leads to 50 * 10k + 50*10 * 10k = apprx 5.5 M
* <p/>
* If isClassCache and isInstanceCache are set, classes and instances cannot be GC.
* <p/>
* It is possible to run offline mode on the dumped class in _temp as well to check the GC usage.
*/
public class MemUsageTest extends TestCase {
private static boolean areClassWritten = false;
/**
* a statci byte[] array for generated class to have a larger mem fooprint
*/
private final static int CLASS_KSIZE = 10;
/**
* a instance array field for generated class to have a larger mem fooprint
*/
private final static int INSTANCE_KSIZE = 10;
/**
* each generated class has this number of sayHello<XX>() instance methods
*/
private final static int HELLO_METHOD_COUNT = 30;
/**
* if set, created class are kept to avoid GC during the test
*/
private boolean isClassCache = true;
/**
* if set, created instances are kept to avoid GC during the test
*/
private boolean isInstanceCache = true;
/**
* number of class HelloXX created thru bcel for the test
*/
private int classFactor;
/**
* number of instance of each HelloXX class instanciated for the test
*/
private int instanceFactor;
/**
* class cache
*/
private List classCache = new ArrayList();
/**
* instance cache
*/
private List instanceCache = new ArrayList();
/**
* Create a new test
*/
public MemUsageTest(String s, int classFactor, int instanceFactor, boolean isClassCache, boolean isInstanceCache) {
super(s);
this.classFactor = classFactor;
this.instanceFactor = instanceFactor;
this.isClassCache = isClassCache;
this.isInstanceCache = isInstanceCache;
}
/**
* Creates a Hello implementation class file
*
* @param dir where to store generated file
* @param className
*/
private void createClassFile(String dir, String className) {
// class classNameXX implements Hello
ClassGen cg = new ClassGen(
className, "java.lang.Object",
"<generated>", Constants.ACC_PUBLIC | Constants.ACC_SUPER,
new String[]{Hello.class.getName()}
);
ConstantPoolGen cp = cg.getConstantPool();
InstructionFactory factory = new InstructionFactory(cg);
// private byte[] buffer = new byte[XXX];
FieldGen field = new FieldGen(Constants.ACC_PRIVATE, new ArrayType(Type.BYTE, 1), "buffer", cp);
cg.addField(field.getField());
// private static byte[] sbuffer = new byte[XXX];
field = new FieldGen(Constants.ACC_PRIVATE | Constants.ACC_STATIC, new ArrayType(Type.BYTE, 1), "sbuffer", cp);
cg.addField(field.getField());
// <init> to initialize buffer field
InstructionList il = new InstructionList();
MethodGen method = new MethodGen(
Constants.ACC_PUBLIC, Type.VOID, Type.NO_ARGS,
new String[]{}, "<init>", className, il, cp
);
il.append(factory.createLoad(Type.OBJECT, 0));
il.append(
factory.createInvoke("java.lang.Object", "<init>", Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL)
);
il.append(factory.createLoad(Type.OBJECT, 0));
il.append(new PUSH(cp, INSTANCE_KSIZE * 1000));
il.append(factory.createNewArray(Type.BYTE, (short)1));
il.append(factory.createFieldAccess(className, "buffer", new ArrayType(Type.BYTE, 1), Constants.PUTFIELD));
il.append(factory.createReturn(Type.VOID));
method.setMaxStack();
method.setMaxLocals();
cg.addMethod(method.getMethod());
il.dispose();
// <clinit> to initialize sbuffer field
il = new InstructionList();
method =
new MethodGen(Constants.ACC_STATIC, Type.VOID, Type.NO_ARGS, new String[]{}, "<clinit>", className, il, cp);
il.append(new PUSH(cp, CLASS_KSIZE * 1000));
il.append(factory.createNewArray(Type.BYTE, (short)1));
il.append(factory.createFieldAccess(className, "sbuffer", new ArrayType(Type.BYTE, 1), Constants.PUTSTATIC));
il.append(factory.createReturn(Type.VOID));
method.setMaxStack();
method.setMaxLocals();
cg.addMethod(method.getMethod());
il.dispose();
// sayHello<XX> 0..()
for (int i = 0; i < HELLO_METHOD_COUNT; i++) {
method = new MethodGen(
Constants.ACC_PUBLIC, // access flags
Type.STRING, // return type
new Type[]{}, // arg type
new String[]{}, // arg names
"sayHello" + i, className, // method, class
il, cp
);
il.append(new PUSH(cp, "sayHello" + i));
il.append(factory.createReturn(Type.STRING));
method.setInstructionList(il);
method.setMaxLocals();
method.setMaxStack();
cg.addMethod(method.getMethod());
il.dispose();
}
// dump in dir
try {
cg.getJavaClass().dump(dir + File.separator + className.replace('.', File.separatorChar) + ".class");
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* Assumes cl classloader has access to classPrefix + 1..classFactor classes Call operation on them Each call is a
* JUnit assertion
*/
private void callClassesOneByOne(ClassLoader cl, String classPrefix) throws Exception {
Class klass = null;
Hello instance = null;
for (int i = 1; i <= classFactor; i++) {
klass = Class.forName(classPrefix + i, true, cl);
if (isClassCache) {
classCache.add(klass);
}
for (int j = 1; j <= instanceFactor; j++) {
instance = (Hello)klass.newInstance();
if (isInstanceCache) {
instanceCache.add(instance);
}
for (int k = 0; k < HELLO_METHOD_COUNT; k++) {
//System.out.print(":");
assertEquals(
"before sayHello" + k + " after",
klass.getMethod("sayHello" + k, new Class[]{}).invoke(instance, new Object[]{})
);
///*no aspect*/assertEquals("sayHello"+k, klass.getAdvice("sayHello"+k, new Class[]{}).invoke(instance, new Object[]{}));
}
}
}
}
/**
* Continue calling class instances Can be used with profiling tool to check memory fooprint
*/
private void continueCalls() {
if (!isInstanceCache) {
return;
}
Hello instance = null;
while (true) {
for (int i = 0; i < instanceCache.size(); i++) {
instance = (Hello)instanceCache.get(i);
for (int k = 0; k < HELLO_METHOD_COUNT; k++) {
try {
assertEquals(
"before sayHello" + k + " after",
instance.getClass().getMethod("sayHello" + k, new Class[]{}).invoke(
instance, new Object[]{}
)
);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
try {
Thread.sleep(200);
System.out.print(".");
}
catch (Exception e) {
;
}
}
}
/**
* Release cached instances and classes
*/
private void releaseCache() {
while (!instanceCache.isEmpty()) {
instanceCache.remove(0);
}
while (!classCache.isEmpty()) {
classCache.remove(0);
}
}
/**
* Creates classFactor classes in dir whith name classPrefix + 1..classFactor
*/
private void createClassFiles(String dir, String classPrefix) {
for (int i = 1; i <= classFactor; i++) {
createClassFile(dir, classPrefix + i);
}
}
/**
* Run with forced weaving thru custom classloader -Daspectwerkz.definition.file=src\test\test-xmldef.xml
* -Daspectwerkz.transform.verbose=yes -Daspectwerkz.transform.dump=test..*
*/
public void runThruWeavingClassLoader() throws Exception {
ClassLoader cl = new WeavingClassLoader(
new URL[]{(new File("_temp")).toURL()}, ClassLoader.getSystemClassLoader()
);
createClassFiles("_temp", "atest");
long ms = System.currentTimeMillis();
callClassesOneByOne(cl, "atest");
System.out.println("completed in: " + (System.currentTimeMillis() - ms));
//continueCalls();// uncomment me if needed
releaseCache();// uncomment me if needed
}
/**
* Run as normal Is weaved thru online mode
*/
public void runThruStandardClassLoader() throws Exception {
ClassLoader cl = new URLClassLoader(
new URL[]{(new File("_temp")).toURL()}, ClassLoader.getSystemClassLoader()
);
createClassFiles("_temp", "HelloClass");
long ms = System.currentTimeMillis();
callClassesOneByOne(cl, "HelloClass");
System.out.println("completed in: " + (System.currentTimeMillis() - ms));
releaseCache();
}
/**
* Test hook
*/
public void testLongRun() throws Exception {
runThruStandardClassLoader();
}
public synchronized void setUp() {
if (!areClassWritten) {
//System.out.println("creating");
createClassFile("_temp", "HelloClass");
areClassWritten = true;
//System.out.println("created");
}
}
public void tearDown() {
//todo clean _temp
}
//-- junit hooks --//
public static void main(String[] args) throws Throwable {
//junit.textui.TestRunner.run(suite());
// uncomment for inside IDE use
MemUsageTest me = new MemUsageTest("test", 10, 20, true, true);
me.createClassFiles("_temp", "HelloClass");
me.runThruWeavingClassLoader();
}
public static junit.framework.Test suite() {
TestSuite suite = new TestSuite();
suite.addTest(new MemUsageTest("testLongRun", 10, 20, true, true));
return suite;
}
}