/*
* This file is part of the Haven & Hearth game client.
* Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and
* Björn Johannessen <johannessen.bjorn@gmail.com>
*
* Redistribution and/or modification of this file is subject to the
* terms of the GNU Lesser General Public License, version 3, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Other parts of this source tree adhere to other copying
* rights. Please see the file `COPYING' in the root directory of the
* source tree for details.
*
* A copy the GNU Lesser General Public License is distributed along
* with the source tree of which this file is a part in the file
* `doc/LPGL-3'. If it is missing for any reason, please see the Free
* Software Foundation's website at <http://www.fsf.org/>, or write
* to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package haven;
import java.util.*;
import java.io.*;
public class Profiler {
private static Loop loop;
public final Thread th;
private boolean enabled;
public Profiler(Thread th) {
this.th = th;
}
public Profiler() {
this(Thread.currentThread());
}
public void enable() {
if(Thread.currentThread() != th)
throw(new RuntimeException("Enabled from non-owning thread"));
if(enabled)
throw(new RuntimeException("Enabled when already enabled"));
if(loop == null) {
synchronized(Loop.class) {
if(loop == null) {
loop = new Loop();
loop.start();
}
}
}
synchronized(loop.current) {
loop.current.add(this);
}
enabled = true;
}
public void disable() {
if(Thread.currentThread() != th)
throw(new RuntimeException("Disabled from non-owning thread"));
if(!enabled)
throw(new RuntimeException("Disabled when already disabled"));
synchronized(loop.current) {
loop.current.remove(this);
}
enabled = false;
}
public static class Function {
public final String cl, nm;
public int dticks, iticks;
public Map<Function, Integer> tticks = new HashMap<Function, Integer>();
public Map<Function, Integer> fticks = new HashMap<Function, Integer>();
public Map<Integer, Integer> lticks = new HashMap<Integer, Integer>();
public Function(String cl, String nm) {
this.cl = cl;
this.nm = nm;
}
public Function(StackTraceElement f) {
this(f.getClassName(), f.getMethodName());
}
public boolean equals(Object bp) {
if(!(bp instanceof Function))
return(false);
Function b = (Function)bp;
return(b.cl.equals(cl) && b.nm.equals(nm));
}
private int hc = 0;
public int hashCode() {
if(hc == 0)
hc = cl.hashCode() * 31 + nm.hashCode();
return(hc);
}
}
private Map<Function, Function> funs = new HashMap<Function, Function>();
private int nticks = 0;
private Function getfun(StackTraceElement f) {
Function key = new Function(f);
Function ret = funs.get(key);
if(ret == null) {
ret = key;
funs.put(ret, ret);
}
return(ret);
}
protected void tick(StackTraceElement[] bt) {
nticks++;
Function pf = getfun(bt[0]);
pf.dticks++;
if(pf.lticks.containsKey(bt[0].getLineNumber()))
pf.lticks.put(bt[0].getLineNumber(), pf.lticks.get(bt[0].getLineNumber()) + 1);
else
pf.lticks.put(bt[0].getLineNumber(), 1);
for(int i = 1; i < bt.length; i++) {
StackTraceElement f = bt[i];
Function fn = getfun(f);
fn.iticks++;
if(fn.tticks.containsKey(pf))
fn.tticks.put(pf, fn.tticks.get(pf) + 1);
else
fn.tticks.put(pf, 1);
if(pf.fticks.containsKey(fn))
pf.fticks.put(fn, pf.fticks.get(fn) + 1);
else
pf.fticks.put(fn, 1);
pf = fn;
}
System.err.print(".");
}
public void outputlp(OutputStream out, String cl, String fnm) {
Function fn = funs.get(new Function(cl, fnm));
if(fn == null)
return;
Map<Integer, Integer> lt = fn.lticks;
PrintStream p = new PrintStream(out);
List<Integer> lines = new ArrayList<Integer>(lt.keySet());
Collections.sort(lines);
for(int ln : lines) {
p.printf("%d: %d\n", ln, lt.get(ln));
}
p.println();
}
public void output(OutputStream out) {
PrintStream p = new PrintStream(out);
List<Function> funs = new ArrayList<Function>(this.funs.keySet());
Collections.sort(funs, new Comparator<Function>() {
public int compare(Function a, Function b) {
return(b.dticks - a.dticks);
}
});
p.println("Functions sorted by direct ticks:");
for(Function fn : funs) {
if(fn.dticks < 1)
continue;
p.print(" ");
String nm = fn.cl + "." + fn.nm;
p.print(nm);
for(int i = nm.length(); i < 60; i++)
p.print(" ");
p.printf("%6d (%5.2f%%)", fn.dticks, 100.0 * (double)fn.dticks / (double)nticks);
p.println();
}
p.println();
Collections.sort(funs, new Comparator<Function>() {
public int compare(Function a, Function b) {
return((b.iticks + b.dticks) - (a.iticks + a.dticks));
}
});
p.println("Functions sorted by direct and indirect ticks:");
for(Function fn : funs) {
p.print(" ");
String nm = fn.cl + "." + fn.nm;
p.print(nm);
for(int i = nm.length(); i < 60; i++)
p.print(" ");
p.printf("%6d (%5.2f%%)", fn.iticks + fn.dticks, 100.0 * (double)(fn.iticks + fn.dticks) / (double)nticks);
p.println();
}
p.println();
p.println("Per-function time spent in callees:");
for(Function fn : funs) {
p.printf(" %s.%s\n", fn.cl, fn.nm);
List<Map.Entry<Function, Integer>> cfs = new ArrayList<Map.Entry<Function, Integer>>(fn.tticks.entrySet());
if(fn.dticks > 0)
cfs.add(new AbstractMap.SimpleEntry<Function, Integer>(null, fn.dticks));
Collections.sort(cfs, new Comparator<Map.Entry<Function, Integer>>() {
public int compare(Map.Entry<Function, Integer> a, Map.Entry<Function, Integer> b) {
return(b.getValue() - a.getValue());
}
});
for(Map.Entry<Function, Integer> cf : cfs) {
p.print(" ");
String nm;
if(cf.getKey() == null)
nm = "<direct ticks>";
else
nm = cf.getKey().cl + "." + cf.getKey().nm;
p.print(nm);
for(int i = nm.length(); i < 60; i++)
p.print(" ");
p.printf("%6d (%5.2f%%)", cf.getValue(), 100.0 * (double)cf.getValue() / (double)(fn.dticks + fn.iticks));
p.println();
}
p.println();
}
p.println();
p.println("Per-function time spent by caller:");
for(Function fn : funs) {
p.printf(" %s.%s\n", fn.cl, fn.nm);
List<Map.Entry<Function, Integer>> cfs = new ArrayList<Map.Entry<Function, Integer>>(fn.fticks.entrySet());
Collections.sort(cfs, new Comparator<Map.Entry<Function, Integer>>() {
public int compare(Map.Entry<Function, Integer> a, Map.Entry<Function, Integer> b) {
return(b.getValue() - a.getValue());
}
});
for(Map.Entry<Function, Integer> cf : cfs) {
p.print(" ");
String nm = cf.getKey().cl + "." + cf.getKey().nm;
p.print(nm);
for(int i = nm.length(); i < 60; i++)
p.print(" ");
p.printf("%6d (%5.2f%%)", cf.getValue(), 100.0 * (double)cf.getValue() / (double)(fn.dticks + fn.iticks));
p.println();
}
p.println();
}
}
public void output(String path) {
try {
OutputStream out = new FileOutputStream(path);
try {
output(out);
} finally {
out.close();
}
} catch(IOException e) {
e.printStackTrace();
}
}
private static class Loop extends HackThread {
private Collection<Profiler> current = new LinkedList<Profiler>();
Loop() {
super("Profiling thread");
setDaemon(true);
}
public void run() {
try {
while(true) {
Thread.sleep(100);
Collection<Profiler> copy;
synchronized(current) {
copy = new ArrayList<Profiler>(current);
}
for(Profiler p : copy) {
StackTraceElement[] bt = p.th.getStackTrace();
if(!p.enabled)
continue;
p.tick(bt);
}
}
} catch(InterruptedException e) {
}
}
}
}