/***
 * ASM performance test: measures the performances of asm package
 * Copyright (c) 2000-2011 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.objectweb.asm;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodNode;

/*
 * Created on Nov 30, 2004-2011 as part of ASMPerf by treffer
 */

/**
 * Memory performances tests for tree package.
 * 
 * @author treffer
 */
public class ASMMemTest {

    public static void main(final String[] args) {
        if (args.length < 2) {
            System.out
                    .println("java ASMMemTest <jar-file> <number-of-classes>");
            System.exit(1);
        }

        Runtime runtime = Runtime.getRuntime();
        memDown(runtime);
        System.out.println("Initial memory load: "
                .concat(memFormat(getUsedMem(runtime))));

        LinkedList<byte[]> fileData = new LinkedList<byte[]>();
        int limit = Integer.parseInt(args[1]);
        try {
            long totalSize = 0;
            JarInputStream jar = new JarInputStream(
                    new FileInputStream(args[0]));
            JarEntry entry = jar.getNextJarEntry();
            while (fileData.size() < limit && entry != null) {
                String name = entry.getName();
                if (name.endsWith(".class")) {
                    if (entry.getSize() != -1) {
                        int len = (int) entry.getSize();
                        byte[] data = new byte[len];
                        int l = jar.read(data);
                        assert l == len;
                        fileData.add(data);
                        totalSize += data.length;
                    } else {
                        System.err.println("No jar-entry size given... "
                                + "Unimplemented, jar file not supported");
                    }
                }
                entry = jar.getNextJarEntry();
            }
            System.out.println(memFormat(totalSize) + " class data, ~"
                    + memFormat(totalSize / limit) + " per class.");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        ArrayList<ClassNode> result = new ArrayList<ClassNode>(fileData.size());
        long startmem;

        for (int i = 0; i < 10; i++) {
            System.out.println("\n> Run ".concat(Integer.toString(i + 1)));
            Iterator<byte[]> files = fileData.iterator();
            result.clear();
            memDown(runtime);
            System.out.println("Empty memory load: "
                    .concat(memFormat(startmem = getUsedMem(runtime))));

            long time = -System.currentTimeMillis();
            while (files.hasNext()) {
                byte data[] = files.next();
                ClassReader reader = new ClassReader(data);
                ClassNode clazz = new ClassNode();
                reader.accept(clazz, 0);
                result.add(clazz);
            }
            time += System.currentTimeMillis();

            memDown(runtime);
            System.out.println("Time: ".concat(timeFormat(time)));
            System.out.println("Final memory load: "
                    .concat(memFormat(getUsedMem(runtime))));
            System.out.println("ASM memory load: "
                    .concat(memFormat(getUsedMem(runtime) - startmem)));
            for (int j = 0; j < limit; j++) {
                ClassNode clazz = result.get(j);
                List<MethodNode> l = clazz.methods;
                for (int k = 0, lim = l.size(); k < lim; k++) {
                    MethodNode m = l.get(k);
                    InsnList insn = m.instructions;
                    if (insn != null) {
                        insn.clear();
                    }
                }
            }
            memDown(runtime);
            System.out.println("ASM memory load (removed method code): "
                    .concat(memFormat(getUsedMem(runtime) - startmem)));
        }

    }

    public final static long getUsedMem(final Runtime r) {
        return r.totalMemory() - r.freeMemory();
    }

    public final static String timeFormat(final long time) {
        int min = (int) (time / (60 * 1000));
        int sec = (int) ((time / 1000) % 60);
        int msec = (int) (time % 1000);
        StringBuffer sbuf = new StringBuffer(30);
        if (min > 0) {
            sbuf.append(min);
            sbuf.append("min ");
        }
        if (sec > 0 || min > 0) {
            sbuf.append(sec);
            sbuf.append("s ");
        }
        if (msec > 0 || sec > 0 || min > 0) {
            sbuf.append(msec);
            sbuf.append("ms ");
        }
        sbuf.append('(');
        sbuf.append(time);
        sbuf.append("ms)");
        return sbuf.toString();
    }

    public final static String memFormat(final long mem) {
        int gb = (int) ((mem >> 30) & 0x3FF);
        int mb = (int) ((mem >> 20) & 0x3FF);
        int kb = (int) ((mem >> 10) & 0x3FF);
        int bytes = (int) (mem & 0x3FF);
        StringBuffer sbuf = new StringBuffer(30);
        if (gb > 0) {
            sbuf.append(gb);
            sbuf.append("GB ");
        }
        if (mb > 0 || gb > 0) {
            sbuf.append(mb);
            sbuf.append("MB ");
        }
        if (kb > 0 || mb > 0 || gb > 0) {
            sbuf.append(kb);
            sbuf.append("KB ");
        }
        if (bytes > 0 || kb > 0 || mb > 0 || gb > 0) {
            sbuf.append(bytes);
            sbuf.append("bytes ");
        }
        sbuf.append('(');
        sbuf.append(mem);
        sbuf.append("bytes)");
        return sbuf.toString();
    }

    public final static void memDown(final Runtime r) {
        long oldmem;
        do {
            oldmem = getUsedMem(r);
            for (int i = 0; i < 10; i++) {
                // Calling System.gc once is very unsafe
                System.gc();
                try {
                    Thread.sleep(10);
                } catch (InterruptedException ie) {
                }
            }
        } while (getUsedMem(r) < oldmem);
    }
}