package tools.performance; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class CPUSampler { private final List<String> included; private static final CPUSampler instance = new CPUSampler(); private long interval; private SamplerThread sampler; private final Map<StackTrace, Integer> recorded; private int totalSamples; public CPUSampler() { this.included = new LinkedList(); this.interval = 5L; this.sampler = null; this.recorded = new HashMap(); this.totalSamples = 0; } public static CPUSampler getInstance() { return instance; } public void setInterval(long millis) { this.interval = millis; } public void addIncluded(String include) { for (String alreadyIncluded : this.included) { if (include.startsWith(alreadyIncluded)) { return; } } this.included.add(include); } public void reset() { this.recorded.clear(); this.totalSamples = 0; } public void start() { if (this.sampler == null) { this.sampler = new SamplerThread(); this.sampler.start(); } } public void stop() { if (this.sampler != null) { this.sampler.stop(); this.sampler = null; } } public SampledStacktraces getTopConsumers() { List ret = new ArrayList(); Set<Entry<StackTrace, Integer>> entrySet = this.recorded.entrySet(); for (Map.Entry entry : entrySet) { ret.add(new StacktraceWithCount(((Integer) entry.getValue()), (StackTrace) entry.getKey())); } Collections.sort(ret); return new SampledStacktraces(ret, this.totalSamples); } public void save(Writer writer, int minInvocations, int topMethods) throws IOException { SampledStacktraces topConsumers = getTopConsumers(); StringBuilder builder = new StringBuilder(); builder.append("Top Methods:\r\n"); for (int i = 0; (i < topMethods) && (i < topConsumers.getTopConsumers().size()); i++) { builder.append(((StacktraceWithCount) topConsumers.getTopConsumers().get(i)).toString(topConsumers.getTotalInvocations(), 1)); } builder.append("\r\nStack Traces:\r\n"); writer.write(builder.toString()); writer.write(topConsumers.toString(minInvocations)); writer.flush(); } private void consumeStackTraces(Map<Thread, StackTraceElement[]> traces) { for (Map.Entry trace : traces.entrySet()) { int relevant = findRelevantElement((StackTraceElement[]) trace.getValue()); if (relevant != -1) { StackTrace st = new StackTrace((StackTraceElement[]) trace.getValue(), relevant, ((Thread) trace.getKey()).getState()); Integer i = this.recorded.get(st); this.totalSamples += 1; if (i == null) { this.recorded.put(st, 1); } else { this.recorded.put(st, i + 1); } } } } private int findRelevantElement(StackTraceElement[] trace) { if (trace.length == 0) { return -1; } if (this.included.isEmpty()) { return 0; } int firstIncluded = -1; for (String myIncluded : this.included) { for (int i = 0; i < trace.length; i++) { StackTraceElement ste = trace[i]; if ((!ste.getClassName().startsWith(myIncluded)) || ((i >= firstIncluded) && (firstIncluded != -1))) { continue; } firstIncluded = i; break; } } if ((firstIncluded >= 0) && (trace[firstIncluded].getClassName().equals("tools.performance.CPUSampler$SamplerThread"))) { return -1; } return firstIncluded; } public static class SampledStacktraces { List<CPUSampler.StacktraceWithCount> topConsumers; int totalInvocations; public SampledStacktraces(List<CPUSampler.StacktraceWithCount> topConsumers, int totalInvocations) { this.topConsumers = topConsumers; this.totalInvocations = totalInvocations; } public List<CPUSampler.StacktraceWithCount> getTopConsumers() { return this.topConsumers; } public int getTotalInvocations() { return this.totalInvocations; } @Override public String toString() { return toString(0); } public String toString(int minInvocation) { StringBuilder ret = new StringBuilder(); for (CPUSampler.StacktraceWithCount swc : this.topConsumers) { if (swc.getCount() >= minInvocation) { ret.append(swc.toString(this.totalInvocations, 2147483647)); ret.append("\r\n"); } } return ret.toString(); } } public static class StacktraceWithCount implements Comparable<StacktraceWithCount> { private final int count; private final CPUSampler.StackTrace trace; public StacktraceWithCount(int count, CPUSampler.StackTrace trace) { this.count = count; this.trace = trace; } public int getCount() { return this.count; } public StackTraceElement[] getTrace() { return this.trace.getTrace(); } @Override public int compareTo(StacktraceWithCount o) { return -Integer.valueOf(this.count).compareTo(o.count); } @Override public boolean equals(Object oth) { if (!(oth instanceof StacktraceWithCount)) { return false; } StacktraceWithCount o = (StacktraceWithCount) oth; return this.count == o.count; } @Override public String toString() { return this.count + " Sampled Invocations\r\n" + this.trace.toString(); } private double getPercentage(int total) { return Math.round(this.count / total * 10000.0D) / 100.0D; } public String toString(int totalInvoations, int traceLength) { return this.count + "/" + totalInvoations + " Sampled Invocations (" + getPercentage(totalInvoations) + "%) " + this.trace.toString(traceLength); } } private class SamplerThread implements Runnable { private boolean running = false; private boolean shouldRun = false; private Thread rthread; private SamplerThread() { } public void start() { if (!this.running) { this.shouldRun = true; this.rthread = new Thread(this, "CPU Sampling Thread"); this.rthread.start(); this.running = true; } } public void stop() { this.shouldRun = false; this.rthread.interrupt(); try { this.rthread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void run() { while (this.shouldRun) { CPUSampler.this.consumeStackTraces(Thread.getAllStackTraces()); try { Thread.sleep(CPUSampler.this.interval); } catch (InterruptedException e) { return; } } } } private static class StackTrace { private final StackTraceElement[] trace; private final Thread.State state; public StackTrace(StackTraceElement[] trace, int startAt, Thread.State state) { this.state = state; if (startAt == 0) { this.trace = trace; } else { this.trace = new StackTraceElement[trace.length - startAt]; System.arraycopy(trace, startAt, this.trace, 0, this.trace.length); } } @Override public boolean equals(Object obj) { if (!(obj instanceof StackTrace)) { return false; } StackTrace other = (StackTrace) obj; if (other.trace.length != this.trace.length) { return false; } if (other.state != this.state) { return false; } for (int i = 0; i < this.trace.length; i++) { if (!this.trace[i].equals(other.trace[i])) { return false; } } return true; } @Override public int hashCode() { int ret = 13 * this.trace.length + this.state.hashCode(); for (StackTraceElement ste : this.trace) { ret ^= ste.hashCode(); } return ret; } public StackTraceElement[] getTrace() { return this.trace; } @Override public String toString() { return toString(-1); } public String toString(int traceLength) { StringBuilder ret = new StringBuilder("State: "); ret.append(this.state.name()); if (traceLength > 1) { ret.append("\r\n"); } else { ret.append(" "); } int i = 0; for (StackTraceElement ste : this.trace) { i++; if (i > traceLength) { break; } ret.append(ste.getClassName()); ret.append("#"); ret.append(ste.getMethodName()); ret.append(" (Line: "); ret.append(ste.getLineNumber()); ret.append(")\r\n"); } return ret.toString(); } } }